My first not-completely-trivial task in Scala is to implement the equivalent of the F# pipe operator.
Quick Explanation of the Pipe Operator
The pipe operator is syntactic sugar that makes certain patterns of code easier to read. When generating a final result by a sequence of value calculations its easier to read the sequence from left -to-right, but Scala only supports passing values to functions in function(value) form, which basically forces the eyes to read the sequence in right-to-left order — rather unnatural.
Here is one way of expressing a sequence of calculations, relying on local vals to hold intermediate sequence values:
def shuffle(str: String): String = . . .
def randomize(str: String): String = . . .
def camelCase(str: String): String = . . .
def futzWith(str: String): String = . . .
def applySeveralStringTransforms(str: String): String = {
val shuffled = shuffle(str)
val randomized = randomize(shuffled)
val camelCased = camelCase(randomized)
futzWith(camelCased)
}
Which reads easy enough — the result of the function is the result of shuffling, randomizing, camel-casing and finally futzing with the original String. And here’s the equivalent calculation as a single expression, without local vals. Note how the sequence of calculations is performed from right to left — first shuffle, then randomize, etc.
def applySeveralStringTransforms(str: String): String = {
futzWith(camelCase(randomize(shuffle(str)))
}
A pipe operator allows you to pass the result of an expression to the right, preserving left-to-right visual order while still being semantically equivalent to either form above:
def applySeveralStringTransforms(str: String): String = {
str |> shuffle |> randomize |> camelCase |> futzWith
}
Attempt # 1: Right-Associative Operator
My first attempt has me defining a right-associative operator, using Odersky’s pimp-my-library technique, to generate a temporary PipeFunc object which has a “|>:” operator. That is, I’m using a couple tricks to get the compiler to automatically rewrite this
like this
(new PipeFunc(func)).|>:(x)
Since my operator “|>:” ends with a colon, Scala treats it as a “right-associative” function — a function of the right-operand “func”, rather than a function of the left-operand “x”. A very short-lived PipeFunc object gets created by an implicit conversion, and this object has a method named “|>:” that gets applied.
Here’s the definition of my operator:
object Pipeline {
implicit def toPipeFunc[X, Y](func: X => Y) = new PipeFunc(func)
class PipeFunc[X, Y](func: X => Y) {
def |>:(value: X): Y = func(value)
}
}
There are immediate problems with this approach. Because of how Scala 2.7.5 (the version I am using) parses this code, it is apparently unable to recognize that the right-hand side of |>: is a function reference. So the following actually causes a compiler error:
// complains "|>:" is not a function of type Unit
"Hello, World!" |>: println
I’m not sure why this is, but using parens fixes the problem, but makes the expression too surprising and ugly for my taste. I’m going to have to go back to the drawing board…
Attempt #2: Left-Associative Operator
Instead of augmenting the function argument on the right, I’ll try augmenting the value argument on the left with a “|>” operator. I’ll use the same pimp-my-library technique to use an implicit conversion of the left-hand object to a temporary object that has a “|>” operator, which takes a function as its right-hand argument. That is, I’m using a couple tricks to get the compiler to automatically convert this
to this
(new PipeLink(x)).|>(func)
Here’s my code for the operator definition:
object Pipeline {
implicit def toPipeLink[X](v: X): PipeLink[X] = new PipeLink[X](v)
class PipeLink[X](value: X) {
def |>[Y](func: X => Y): Y = func(value)
}
}
OK, works correctly, and doesn’t have any of the problems that the right-associative operator had.
I do have a hidden performance issue: creation of an very-short-lived temporary object. Consider how the the following single expression gets rewritten by the Scala compiler:
str |> shuffle |> randomize |> camelCase |> futzWith
to this:
(new PipeLink((new PipeLink((new PipeLink((new PipeLink(str)).
|>(shuffle)).|>(randomize)).|>(camelCase)).|>(futzWith)
Ugly – obviously; wastefully creates too many objects – also true. 4 temporary PipeLinks created and thrown away. The JVM GC is pretty good at handling short-lived objects, but this is terribly wasteful. I can’t think of a way of getting rid of temporary objects — any suggestions greatly appreciated!
Smarter Than Me
Only after implementing did I find that Steve Gilham had already posted his implementation of the pipe operator. He immediately went with a left-associative operator implementation. Must be a clever guy! Both his solution and mine suffer from creating a temp object with each invocation of the pipe operator.
// complains “|>:” is not a function of type Unit