Leveraging message passing to do currying in ruby
I’m not much of a ruby guy, but I had the inkling to play with it this weekend. The first thing I do when I’m in a new language is try to map constructs that I’m familiar with, from basic stuff like object instantiation, singletons, inheritance, to more complicated paradigms like lambdas and currying.
I came across this blog post that shows that ruby has a way to auto curry lambdas, which is actually pretty awesome. However, I was a little confused by the syntax
a.send(fn, b)
I’m more used to ML style where you would do
fn a b
So what is a.send
doing?
Message passing
Ruby exposes its dynamic dispatch as a message passing mechanism (like objective c), so you can send “messages” to objects. It’s like being able to say “hey, execute this function (represented by a string) on this context”.
If you think of it that way, then a.send(fn, b)
translates to “execute function ‘fn’ on the context a, with the argument of b”. This means that fn better exist on the context of ‘a’.
As an example, this curries the multiplication function:
apply\_onContext = lambda do |fn, a, b|
a.send(fn, b)
end
mult = apply\_onContext.curry.(:\*, 5)
puts mult.(2)
This prints out 10
. First a lambda is created that sends a message to the object ‘a’ asking it to execute the the function *
(represented as an interned string).
Then we can leverage the curry function to auto curry the lambda for us creating almost F# style curried functions. The syntax of “.(“ is a shorthand of .call syntax which executes a lambda.
If we understand message passing we can construct other lambdas now too:
class Test
def add(x, y)
x + y
end
def addOne
apply\_onClass = lambda do |fn, x, y|
send(fn, x, y)
end
apply\_onClass.curry.(:add, 1)
end
end
puts Test.new.addOne.(4)
This returns a curried lambda that invokes a message :add on the source object.
Getting rid of the dot
Ruby 1.9 doesn’t let you define what ()
does so you are forced to call lambdas with the dot syntax. However, ruby has other interesting features that let you alias a method to another name. It’s like moving the original method to a new name.
You can do this to any method you have access to so you can get the benefits of method overriding without needing to actually do inheritance.
Taking advantage of this you can actually hook into the default missing message exception on object (which is invoked when a “message” isn’t caught). Catching the missing method exception and then executing a .call on the object (if it accepts that message) lets us fake the parenthesis.
Here is a blog post that shows how to do it.
Obviously it sucks to leverage exception handling, but hey, still neat.
Conclusion
While nowhere near as succinct as f#
let addOne = (+) 1
But learning new things about other languages is interesting :)