22
FEB2006
Currying
All the examples in this chapter are trivially translated (switch sub { ... }
to lambda { ... }
). Ironically, I have never seen a chunk of idiomatic Ruby do anything like this. Rubyists clearly favor blocks for this sort of work. Have a look at the stream addition and multiplication examples of this chapter, for example. You can also see this when MJD trying to create a suitable inject()
for Perl (he calls it reduce/fold).
Another interesting point about this chapter is how much of it is spent warring with Perl's syntax. MJD really struggles to introduce a block-like syntax for curried methods and is outright defeated in a couple of attempts. I really like how easily Ruby yields to our attempts to reprogram her, in sharp contrast to her sister language.
Continuing that line of thought, here's my best effort at the Poor Man's Currying library:
#!/usr/bin/env ruby -w
class Proc
def curry(&args_munger)
lambda { |*args| call(*args_munger[args]) }
end
end
class Object
def curry(new_name, old_name, &args_munger)
([Class, Module].include?(self.class) ? self : self.class).class_eval do
define_method(new_name) { |*args| send(old_name, *args_munger[args]) }
end
end
end
Unlike the Perl, this feels like very natural Ruby to me. You could argue that I struggle with Ruby's syntax because it's not easy to curry a block-taking method, but that seems like a minor issue. (This is fixed in Ruby 1.9, which adds Proc#curry
.)
Here are examples of me playing with my toy:
#!/usr/bin/env ruby -w
require "curry"
multiply = lambda { |l, r| l * r }
double = multiply.curry { |args| args + [2] }
triple = multiply.curry { |args| args << 3 }
multiply[5, 2] # => 10
double[5] # => 10
triple[5] # => 15
triple["Howdy "] # => "Howdy Howdy Howdy "
class Value
def initialize(value)
@value = value
end
def *(other)
@value * other
end
curry(:double, :*) { [2] }
curry(:triple, :*) { |args| args.unshift(3) }
end
five, howdy = Value.new(5), Value.new("Howdy ")
five * 2 # => 10
five.double # => 10
five.triple # => 15
howdy.triple # => "Howdy Howdy Howdy "
I am purposefully trying to show the library's flexibility in these calls. The version in the book only supports currying a single argument at the front of the list. In 15 lines the Ruby version can completely rewrite arguments as it sees fit and it includes OO support.
You be the final judge, but I stand by my claim that Higher-Order Perl is the quest to bring many Rubyisms to Perl.
If you want to go deeper down the rabbit hole of currying in Ruby, I recommend you check out the Ruby library Murray. If you want to see a more Rubyish example of the FlatDB
MJD builds in these pages, also see my article on code blocks.
Comments (4)
-
Gregory November 22nd, 2006 Reply Link
The code is super clean, though i did need to get into 'functional
programming mode' to get it (took me about 5 minutes to understand
what was going on).Currying is academically super interesting, but I think it's more
useful in languages like Haskell.If I were to write a double lambda which curried a multiply lambda,
for instance, I'd just do this:multiply = lambda { |a,b| a * b } double = lambda { |a| multiply[a,2] }
I think this is a lot less scary than a generalized currying lib, and
just as powerful.Maybe I'm missing a killer use case though. The method aliasing is
definitely cool:curry(:double, :*) { [2] }
But what does that get me over:
def double(num) num * 2 end
I think the problem with Ruby is it doesn't need stuff like this. :)
-
What I find strange is that the syntax for calling your curried function uses square brackets whereas the syntax for calling regular methods (like
puts
andchop
) uses parens. It's an unfortunate aspect of Ruby's design. Python can do most of the same tricks but both the input and the output of the curry are Python "callables" called with parens.http://www.python.org/dev/peps/pep-0309/
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52549
-
In Python:
>>> import functools >>> five_or_higher = functools.partial(max, 5) >>> five_or_higher(6) 6 >>> five_or_higher(3) 5
According to the Python docs, partial could be implemented like this:
def partial(func, *args, **keywords): def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(fkeywords) return func(*(args + fargs), **newkeywords) newfunc.func = func newfunc.args = args newfunc.keywords = keywords return newfunc
Note that this partial function supports both positional and keyword arguments.
Another interesting version looks like this:
>> from partial import _ >>> addWorld = _ + " world!" >>> addWorld("Hello,") "Hello, world"
-
He only used square brackets as a shortcut (i.e.
Proc#[]
is an alias toProc#call
).You can replace the square brackets
[...]
in his example with.call(...)
and things will work just the same.
-