Stuff about software development, agile and testing

Thursday, January 7, 2010

Ruby inject in Scala

Do you have your favorite line in Ruby. Mine will be

(1..100).inject(&:+)

and I first saw that in Reg Braithwaite blog (very good read)

So what's going on here? Here is the explanation from Reg Braithwaite's blog


Explanation: (1..100) creates a Range. For our purposes, this is equivalent to a collection of the whole numbers from one to one hundred, inclusive (The major way in which it differs from an array for us is that it doesn’t actually require one hundred spots in memory. Ranges are also useful for matching.) The #inject method is called fold or reduce in other languages. Expressed like this, it applies the “+” function to the elements of our range, giving us the sum. Primary school students could tell you an easier way to obtain the sum, of course, but we will ignore that for now.

Ruby doesn’t actually have a function called +. What actually happens is that #inject wants a block. The & operator converts Proc objects into blocks and block into Proc objects. In this case, it tries to convert the symbol :+ into a block. The conversion uses Ruby’s built-in coercion mechanism. That mechanism checks to see whether we have a Proc object. If not, it sends the #to_proc method to the argument to make a Proc. If the Symbol :+ has a #to_proc method, it will be called. In Ruby 1.9 it has a #to_proc method. That method returns a Proc that takes its first argument and sends the + method to it along with any other arguments that may be present.



Well even though there are plenty different way you could add entries in collection, this line of code demonstrates the power of the language and what you could do with it.

Being Scala is my favorite programming language right now I wanted to implement something very similar in it. By default in Scala if you want some apply some binary operator to a range you could do

(1 until 100).reduceLeft { _ + _} and if you want your range to be inclusive then

(1 until 100).inclusive.reduceLeft { _ + _}

But still it is little bit verbose. So with some implicit trick I was able to come quite a bit close to the actual Ruby line above

1 ⟞ 10 inject '+

'⟞' is a unicode character and yes you could define method names with unicode characters. '+ is symbol is Scala. And here is rest of the implementation...

implicit def rich_range(r: Range) = new {
def inject(s:Symbol) = {
s match {
case '+ => r.reduceLeft { _ + _ }
case _ => //not supported yet
}
}
}

implicit def rich_int(start: Int) = new { def ⟞(end:Int) = (start until end).inclusive }




Well again point is not about using binary operator on a collection or range. Its about powerful and expressiveness of a programming language.

Labels