Fun with Procs in Ruby 1.9
Ruby 1.9 adds a lot of features to Proc objects.
Currying is the ability to take a function that accepts n parameters and generate from it one of more functions with some parameter values already filled in. In RUby 1.9, you create a curry-able proc by calling the curry method on it. If you subsequently call this curried proc with fewer parameters than it expects, it will not execute. Instead, it returns a new proc with those parameters already bound.
Let's look at a trivial example. Here's a proc that simply adds two values:
plus = lambda {|a,b| a + b}
puts plus[1,2]
I'm using the [ ] syntax to invoke the proc with arguments, in this case 1 and 2. The code will print 3.
Now let's have some fun.
curried_plus = plus.curry # create two procs based on plus, but with the first parameter # already set to a value plus_two = curried_plus[2] plus_ten = curried_plus[10] puts plus_two[3] puts plus_ten[3]
On line 1, I create a curried version of the plus proc. I then call it twice, but both times I only pass it one parameter. This means it cannot execute the body. Instead, each time it returns a new proc which is like the original, but which has the first parameter preset to either 2 or 10. In the last two lines, I call these two new procs, supplying the missing parameter. This means they can execute normally, and the code outputs 5 and 13.
You can have a lot of fun with currying, but that's not why we're here today.
Over the weekend, Matz added a new method to the Proc class. You can now use Proc#=== as an alias for Proc.call. So, why on earth would you want to do that? Well, remember that === is used to match terms in a case statement. Over of the AimRed blog, they noted that this feature could be used to make the matching in case statements actually execute code. In their example, they manually added the === method to class Proc
class Proc
def ===( *parameters )
self.call( *parameters )
end
end
Then you can write something like
sunday = lambda{ |time| time.wday == 0 }
monday = lambda{ |time| time.wday == 1 }
# and so on...
case Time.now
when sunday
puts "Day of rest"
when monday
puts "work"
# ...
end
See how that works? As Ruby executes the case statement, it looks at each of the parameters of the when clauses in turn. For each, it invokes its === method, passing that method the original case discriminator (Time.now in this example). But with the new === method in class Proc, this will now execute the proc, passing it Time.now as a parameter.
While updating the PickAxe, I noticed that Matz liked this so much that it is now part of 1.9. And it means we can combine this trick with currying to write some fun code:
is_weekday = lambda {|day_of_week, time| time.wday == day_of_week}.curry
sunday = is_weekday[0]
monday = is_weekday[1]
tuesday = is_weekday[2]
wednesday = is_weekday[3]
thursday = is_weekday[4]
friday = is_weekday[5]
saturday = is_weekday[6]
case Time.now
when sunday
puts "Day of rest"
when monday, tuesday, wednesday, thursday, friday
puts "Work"
when saturday
puts "chores"
end
Is this incredibly efficient? Not really :) But it opens up quite an interesting set of possibilities.




I love it, thanks for the demo :)
Posted by: Russell Jones | September 09, 2008 at 11:20 PM
Do you know if Matz have considered the stuff in the followup post that introduces usage of === for select/any/all etc ?
This instantly rings true for me, for example:
%w(ape ball monkey_wrench mall).select /all/
Posted by: yhvd | September 10, 2008 at 07:21 AM
Holy cow! I never thought that Proc#call being aliased to Proc#=== would ever be taken seriously!
Posted by: Farrel | September 10, 2008 at 08:52 AM
I love the concept, but I hate the implementation.
Why not just have case statements either send :=== or :call?
Posted by: Bob Aman | September 10, 2008 at 01:17 PM
I never knew of currying. Very cool.
Posted by: Bradly Feeley | September 10, 2008 at 03:24 PM
This kind of stuff is what making programming with Ruby so fun. Thanks for the demo.
Posted by: Linh Nguyen | September 11, 2008 at 01:31 AM
Wow. Nice right up!
Some questions though: why aren't Proc's curry-able by default? Performance?
Is the curried Proc actually partially evaluated or is it some kind of proxy object that's saved the argument and only actually calls the Proc once it has all of them?
Posted by: AndrewO | September 24, 2008 at 09:53 AM
I'm loving Proc#=== and the examples you build with it, not least because I'm going to take and integrate it (and your example) into a little lib I'm building called Clockwork (http://github.com/emmanuel/clockwork/tree/master ).
Your example is quite close to what Clockwork does, though without the benefit of Proc#===, thus far. Ironically, I started on Clockwork while in Bali with no internet access (wrote it out with a mechanical pencil in long hand), then came back to find this blog post! Some ideas arrive when their time has come, I suppose. But really, get outta my head, Dave! :)
Proc#=== is going to be very useful for things like Election Day--Tuesday after the first Monday in November.
Thanks, Dave!
Posted by: Emmanuel Gomez | October 01, 2008 at 06:27 PM
Andrew: To preserve backwards compatibility, Proc#[] has to throw a TypeError if too few arguments are provided.
irb(main):007:0> p = Proc.new do |a, b| a + b; end
=> #
irb(main):008:0> p[1, 2]
=> 3
irb(main):009:0> p[1]
TypeError: nil can't be coerced into Fixnum
Posted by: Jason Zaugg | October 08, 2008 at 04:26 PM
Or more precicely, pad out the argument list with nils to match the arity of the Proc:
irb(main):010:0> p = Proc.new do |a, b| b.class; end
=> #
irb(main):011:0> p[1, 2]
=> Fixnum
irb(main):012:0> p[1]
=> NilClass
irb(main):013:0> p = Proc.new do |*a| a.class; end
=> #
irb(main):014:0> p[1]
=> Array
irb(main):015:0> p.arity
-1
Posted by: Jason Zaugg | October 08, 2008 at 04:29 PM
Wow, is that some LISP that I observe? Sweet!
Posted by: Aldric | October 14, 2008 at 03:58 PM
Can I translate this article into Chinese? Of course, keep the original link and your name.
Posted by: g.zhen.ning | October 18, 2008 at 06:35 AM