In this post, I look at Thrush, a permuting combinator. A Thrush is defined by the following condition:
Txy = yx
. Thrush reverses the order of evaluation. Raganwald talks about Thrush and its implementations in Ruby in an excellent post quite some time back.Why would you want to reverse the order of evaluation in a computation ? Well, if you value readability of your code, and have been cringing at how a mix of expression oriented pipelines and nested function calls can make your code less readable, then a Thrush may be for you.
Consider the Ruby example that Raganwald discusses in his first example of Thrush implementation.
lambda { |x| x * x }.call((1..100).select(&:odd?).inject(&:+))
The argument to the proc is a pipeline expression that reads nicely from left to right : get the list of numbers from 1 to 100, select the odd ones and add them up. What the proc does is it finds the square of its input number. But the proc invocation is a function call, which, though is the last in sequence to be executed, has to be the first one that you read. You can find the Ruby implementation in Raganwald's blog that transforms the above code to a left-to-right pipeline expression using Thrush.
Let's try to see how we can do the same in Scala ..
In Scala, I can write the above as ..
((x: Int) => (x * x))((1 to 100).filter(_ % 2 != 0).foldLeft(0)(_+_))
Almost the same as the Ruby code above, and has the similar drawback in readability.
Let's define the Scala Thrush combinator ..
case class Thrush[A](x: A) {
def into[B](g: A => B): B = {
g(x)
}
}
Immediately we can write the above invocation as ..
Thrush((1 to 100)
.filter(_ % 2 != 0)
.foldLeft(0)(_ + _))
.into((x: Int) => x * x)
A very simple combinator, a permuting one that pushes the function to where it belongs in the line of readability. If you want to be more succinct, commit the sin of defining an implicit for your use case ..
implicit def int2Thrush(x: Int) = Thrush(x)
and immediately the above transforms to ..
(1 to 100)
.filter(_ % 2 != 0)
.foldLeft(0)(_ + _)
.into((x: Int) => x * x)
Does it read better ?
In fact with this implicit definition, you can go chaining
into
all the way ..(1 to 100)
.filter(_ % 2 != 0)
.foldLeft(0)(_ + _)
.into((x: Int) => x * x)
.into(_ * 2)
While designing domain APIs that need to be expressive, this technique can often come very handy. Here's an example that uses the Thrush combinator to ensure a clean pipeline expression flowing into a piece of DSL.
//..
accounts.filter(_ belongsTo "John S.")
.map(_.calculateInterest)
.filter(_ > threshold)
.foldLeft(0)(_ + _)
.into {x: Int =>
updateBooks journalize(Ledger.INTEREST, x)
}
//..