Ruby as both a functional and an OO language

March 25th, 2006 at 6:51 am

Ruby is a functional language – which means that its functions are first-class objects – functions can be created in runtime, stored in data structures, passed as arguments to other functions and so on. Consider the following example:

debts = [['Alex', 250], ['Mary', 244], ['George', 501]]
debts_after_tax = debts.map {|o| o[1] *= 1.2; o}

The block that follows map is a new function, constructed and evaluated at runtime – passed in as an argument to map. This is an ‘anonymous function’ – it has no name and is created only once to do some job.
Say we want to turn an array of strings into integers. Here’s an example:

 str = "1-800-50-50-68"
 ar = str.split(/-/).map {|o| o.to_i}

Note that, once again, we use map to iterate over all values in an array (that is returned by split) and apply a function to each value. Here, however, we see an actual deficiency in standard Ruby. We simply call one function on each object, why should it go through another new, anonymous function ? In fact, this is easily fixable, and the fix displays Ruby’s Object Oriented nature.

In Ruby everything is an object, including of course arrays. Arrays are derived from Enumerable, which actually defines the map method (and its synonim collect). We can add another form of map right into Enumerable:

module Enumerable

     def mapf(method, *args)
         map {|obj| obj.send(method, *args)}
     end

 end

Now we can simplify the last example to:

str = "1-800-50-50-68"
 ar = str.split(/-/).mapf(:to_i)

mapf is a new mapping method that takes a function name as an argument (represented by a Symbol in Ruby – symbols are generally used to represent names of things), and applies this function to all the values it iterates on.

Note how natural the new call looks. In some functional but non-OO languages, we would have probably done something like:

ar = mapf(:to_i, split(/-/, str))

But since Ruby is fully object oriented, its way is simpler and much easier to understand.

Related posts:

  1. lambda²
  2. Understanding Ruby blocks, Procs and methods
  3. Book review: “The Ruby way” by Hal Fulton
  4. ruby
  5. Book review: “Programming Ruby, 2nd Ed.” by Dave Thomas and Andy Hunt

5 Responses to “Ruby as both a functional and an OO language”

  1. binary42 Says:

    Note that you can go even further with using #to_proc on a Symbol:

    class Symbol
    def to_proc
    lambda {|x| x.send self}
    end
    end

    ['1', '2', '3'].map(&:to_i)

    Now you can use this with map and other functions:

    # -@ is unary -
    [1,2,3].sort_by(&:-@)
    # Another way to #compact
    [true, nil, false, 'hello', nil].reject(&:nil?)

    Of course, you can always just pass a block or use Proc objects.

  2. eliben Says:

    Thank you, this is indeed interestong. I really hope in some future version of Ruby this will be possible without special conversion functions.

  3. Tom Moertel Says:

    Thanks for the article. This line, in particular, is interesting:

    But since Ruby is fully object oriented, it’s way is simpler and much easier to understand [than non-OO functional languages].

    Don’t you think you are overstating the case a bit? Your supporting example suggests that it all comes down to the notation x.f(args) being obviously simpler than f(x,args), but they seem pretty much equivalent.

    Also, in most modern FP languages, programmers don’t write code like the following to nest function calls:

    f(w, g(x, y, h(z, o)))

    Instead, they use function composition and partial application to create clean-looking pipelines:

    (f w . g x y . h z) o

    Cheers,
    Tom

  4. eliben Says:

    The notion of ‘clean looking’ is a matter of personal taste, and I certainly find the object method cleaner here, mostly because it’s obvious that the mapping is applied to an array object.

  5. Gavri FernandezNo Gravatar Says:

    Arrays are derived from Enumerable

    Arrays don’t derive from it – not according to usual OO parlance anyway. Enumerable is actually mixed in. That was probably what you meant. Just thought I’d point it out. Could confuse readers.