Higher-Order Ruby

A Rubyification of the Perl book about functional programming.

22

FEB
2006

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)
  1. Gregory
    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. :)

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
  2. Paul
    Paul November 23rd, 2006 Reply Link

    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 and chop) 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

    1. Reply (using GitHub Flavored Markdown)

      Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

      Ajax loader
    2. Paul
      Paul November 23rd, 2006 Reply Link

      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"
      

      http://www.xoltar.org/2003/jun/25/partialModule.html

      1. Reply (using GitHub Flavored Markdown)

        Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

        Ajax loader
    3. Suraj
      Suraj October 6th, 2007 Reply Link

      He only used square brackets as a shortcut (i.e. Proc#[] is an alias to Proc#call).

      You can replace the square brackets [...] in his example with .call(...) and things will work just the same.

      1. Reply (using GitHub Flavored Markdown)

        Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

        Ajax loader
Leave a Comment (using GitHub Flavored Markdown)

Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

Ajax loader