A forward pipe (“ |> ”) operator in Scala
One of the addictive things from F# is the |> operator defined by
but I've not yet found an equivalent in Scala, even though there are a ton of useful things given by the built-in APIs -- for example, I've not had to implement the usual abstraction to manage an (array, offset, length) combination, when there's Array.slice() there already.
So, just for fun...
with unit test
Refrigerator logic — I later realise that without actually adding any variance annotations the piped-to function can be one that takes any super-class of the value type. A little more thought suggests to me that type-inference does that automagically because it is able to reconcile all inputs into the types in that single expression, and will force the value to the supertype in order to make everything match up.
8 comments :
I really like this. Scala's semicolon inference makes it impossible to lay it out vertically, as we usually see in F#, within a block, but all you have to do is place the whole computation inside parenthesis, making it an expression:
(input
|> ("""\s\s+""".r replaceAllIn(_ : String, " "))
|> doStuff
)
Of course, this is not all that much useful in Scala, as the functions are methods defined on the class o the data. So you really don't need any operator to use map, filter, etc.
Still, I think it would be a nice addition to Scala's library.
Why not just
implicit def toPiped[T] (value:T) = new {
def |>[R] (f : T => R) = f(this.value)
}
@Nodir
Use of implicits in the way suggested results in reflection calls, which are slow. It may not matter, but, then again, it may.
@Daniel
What a pity! Anyway it is inconsistent: other Scala code is allowed to access anonymous class members, but the implicit method return type is Object. The return type could be the anonymous type, which is public btw. At least they could use type casting instead of reflection.
What about composing the functions with andThen?
Instead of:
42 |> makeFloating |> sample
it's
val chainOfEvents = (makeFloating _) andThen sample
val result = chainOfEvents(42)
And of course you can move chainOfEvents somewhere where it's only evaluated once if you need to.
Yeah, the pipe operator is probably best suited to a style somewhat more on the functional side than Scala's sweet spot; one where data and functions are separated, the individual steps are written as short functions on one object, then you push the plain ol' data records of different types through them e.g.
val (v,r, lonsun) = now |> decimalDay |> Sun |> SunLongitude
val (xg, yg, zg) = now |> decimalDay |> Moon |> primaryCentric
to take an example from the little ephemeris applet I wrote a while back.
Most times, the particular stack of transformations on a piece of data are a one-off -- in this case the value of
(decimalDay _) andThen Sun andThen SunLongitude
would only be used in the one place.
On an aesthetic note -- as discussed in the Staircase book, the parser combinators are defined in symbolic operator style, so that they don't drown the grammar. For the same reason that the ~ combinator is not named andThen, Function1.andThen could do with a >> synonym too. There's precedent, after all, in the form of the fold operations having /: and :\ synonyms.
It's part of my standard repertoire, since it lets the order of my code consistently match the order of my operations. Scala code naturally does this when chaining methods or chaining higher-order function calls on collections, and the forward-pipe lets me keep the chain going:
((1 to 300) map square
map (_ % 85)
|> (_.max)
|> println
I found it useful to extend the functionality to tuples:
class PipeExtension[A](a: A) {
def |>[B](func:A => B) = func(a)
}
class TuplePipeExtension[A,B](a:(A,B)) {
def |>[C](func:(A,B) => C) = func(a._1, a._2)
}
class TriplePipeExtension[A,B,C](a:(A,B,C)) {
def |>[D](func:(A,B,C) => D) = func(a._1, a._2, a._3)
}
class QuadruplePipeExtension[A,B,C,D](a:(A,B,C,D)) {
def |>[E](func:(A,B,C,D) => E) = func(a._1, a._2, a._3, a._4)
}
class QuintuplePipeExtension[A,B,C,D,E](a:(A,B,C,D,E)) {
def |>[F](func:(A,B,C,D,E) => F) = func(a._1, a._2, a._3, a._4, a._5)
}
object ImplicitPipe {
implicit def pipe[A](a: A) = new PipeExtension[A](a)
implicit def tuplePipe[A,B](a:(A,B)) = new TuplePipeExtension[A,B](a)
implicit def triplePipe[A,B,C](a:(A,B,C)) = new TriplePipeExtension[A,B,C](a)
implicit def quadruplePipe[A,B,C,D](a:(A,B,C,D)) = new QuadruplePipeExtension[A,B,C,D](a)
implicit def quintuplePipe[A,B,C,D,E](a:(A,B,C,D,E)) = new QuintuplePipeExtension[A,B,C,D,E](a)
}
Post a Comment