6 Making Ruby More Functional

Up to now we’ve focused on providing patterns and tools, inspired by functional languages, that can make your existing Ruby code better. The rationale is simple : the more tools you have up your sleeve, the more likely you will have the right one handy when a new problem presents itself.

We’ve shown that Ruby has all it needs to be able to program in a functional style, but to be quite honest its syntax didn’t always cooperate nicely. Inspired by LISP as it may be, its syntax is still optimized for a more traditional, imperative style of programming.

Oh sure, there’s this one neat trick where passing a single inline lambda into a function can be written very elegantly. But when we already have the lambda (or any Procable object) at hand we now need to insert an extra ampersand. It’s an irksome lack of symmetry.

Another annoyance is Ruby’s optional parentheses when calling methods. Well the not the optional parentheses an sich, I’m as happy to drop them in my code as the next Rubyist, but it means we need to do extra work to distinguish between referencing a method (method(:foo)), versus calling a method (foo). Contrast this with languages like Javascript or Python, where this distinction is made through the presence (foo()) or absence (foo) of parentheses. It also introduces asymmetry when calling lambdas (foo.()) vs methods (foo()).

But Ruby isn’t really at fault here. For one language designers optimize for what they consider to be the most common case, especially when it comes to syntax. If Ruby’s creators hadn’t had strong opinions it wouldn’t have been the Ruby we know and love. But also, and here’s the kicker, there’s nothing here we cannot fix ourselves.

In this chapter we will do whatever it takes to make Ruby a beautiful functional language. In the process we’ll be monkey patching a lot of core classes. Let’s get started, it’s time to break the rules!

A Note on Monkey Patching

Ruby allows any class to be reopened, and any method to be redefined, including those in the core and standard library. This is what makes “magic” Rails code like 7.days.from_now possible. It’s a matter of “re-opening” the Numeric class (the parent class of all types of numbers) and adding the days method.

class Numeric
  def days
    Duration.new(self, :days)
  end
end

It’s also possible to redefine a method that already existed, sometimes with surprising results:

class FixNum
  def *(other)
    self / other
  end
end

10 * 5 # => 2

This comes in handy when a library has a bug and waiting for a fixed version is not an option. In most languages your only choice would be to pull the entire library code into your project, and change your local copy to fix the bug. Not so in Ruby! After loading the library simply redefine the buggy method and you’re done.

Ruby developers refer to these practices affectionately as “monkey patching”. The name implies some derision, because monkey patching is done more often than it should be. When you add methods to Array or String you are essentially creating a dialect of Ruby. Rails gets away with this because there are plenty of Rails developers that have learned this dialect specifically.

Consider carefully if monkey patching is in order before plunging ahead. Is the method as intuitive to your colleagues as it is to you? What if a third-party library defines a method with the same name, but that behaves slightly different?

In general monkey patching, especially extending core classes, is discouraged. But if it’s your project and it makes sense, go for it. Maybe a dialect of Ruby is just what is called for. Monkey patching, like good judgment and restraint, is just another tool in your belt.

6.1 Block and Lambda Arguments

When defining a method that takes a lambda as one of its arguments, we typically use a block argument. This way it’s easy to define the lambda inline where it is used.

# Example of a function that takes a single lambda

# Function decorator that negates the result, in other words,
# given a predicate function, it returns the complement.
def not(&prc)
  ->(*args) {
    !prc.(*args)
  }
end

# Call the method using an inline block
not_false = not do
  false
end

not_false.() # => true

array = []
is_empty  = array.method(:empty?)

# Call the method passing in a Procable, the ampersand is necessary
not_empty = not(&is_empty)

not_empty.() # => false

array << 1
not_empty.() # => true

We can see that Ruby syntax is optimized for methods that take a single lambda, which is defined in-line. When a function expects multiple lambdas, or will usually be called with lambdas that were already defined beforehand, then it makes more sense to simply pass in the lambdas as regular arguments.

# Example of a function that takes multiple lambdas

# Juxtapose several lambdas, the result is a single lambda that calls
# all given lambdas in turn with the same arguments, and returns an
# array of the results
def juxt(*lambdas)
  ->(*args) do
    lambdas.map {|l| l.(*args) }
  end
end

conversions = juxt(
  ->(x) { x.upcase },
  ->(x) { x.capitalize },
  ->(x) { x.downcase }
)

conversions.('hAppY HaPPy')
# => ["HAPPY HAPPY", "Happy happy", "happy happy"]

6.2 Converting to Proc

There is an extra advantage to using a block parameter, with the explicit ampersand, as opposed to simply passing in lambda values. We get the ability to pass something in that is not a Proc, but could be one.

What I’m talking about is implicit conversion, it’s what allows us to write code like:

[1, 2, 3].map(&:next) # => [2, 3, 4]

The :next in this code is a Symbol, it’s definitely not a Proc. For example we can’t call it directly:

:next.(2)
# NoMethodError: undefined method `call' for :next:Symbol

But a symbol knows how to convert itself to a Proc by implementing the to_proc conversion protocol. When Ruby expects a Proc but gets something else, it will try to call to_proc and hopefully get a Proc that way. We call objects that implement to_proc Procable.

next_proc = :next.to_proc
next_proc.(2) # => 3

Here is how Symbol#to_proc would look like if it were written in plain Ruby (the actual implementation is written in C).

class Symbol
  def to_proc
    ->(*args) { args.first.send(self, *args.drop(1)) }
  end
end

These conversion methods are a common theme in Ruby, other examples are to_ary, to_str, and to_path for objects that can be converted to Array, String and Pathname respectively. You don’t usually come across calls to these functions because they happen behind the scenes, that is why they are called implicit conversions. When a core method expects a String you can pass it anything that responds to to_str. Ruby will take the hint and do the conversion for you before proceeding.

It’s a good idea to follow this pattern in our own code. Let’s revisit the juxt method. At the moment we can’t do this:

conversions = juxt(
  :upcase,
  :capitalize,
  :downcase
)

conversions.('hAppY HaPPy')
# NoMethodError: undefined method `call' for :upcase:Symbol

We could give juxt a hand by turning the symbols into procs explicitly:

conversions = juxt(
  :upcase.to_proc,
  :capitalize.to_proc,
  :downcase.to_proc
)

Or just push that conversion into the juxt method itself:

def juxt(*lambdas)
  ->(*args) do
    # was: lambdas.map {|l| l.(*args) }
    lambdas.map {|l| l.to_proc.(*args) }
  end
end

juxt(:upcase, :capitalize, :downcase).('fEEls gOOd')
# => ["FEELS GOOD", "Feels good", "feels good"]

Related to the implicit (to_int, to_str) and explicit (to_i, to_s) conversion protocols are a set of capitalized conversion methods. Let me quickly demonstrate a few:

Integer("7") # => 7
Integer(5.3) # => 5
String(:foo) # => "foo"
String(99)   # => "99"
Array(nil)   # => []
Array(3)     # => [3]
Array([3])   # => [3]

These will try to convert whatever you pass them to the class with the same name, or fail if they can’t find a meaningful conversion. Unfortunately Ruby lacks a Proc() conversion method, so before we proceed let’s add one:

def Proc(prc)
  # Instances of Proc also respond to to_proc, returning themselves
  if prc.respond_to?(:to_proc)
    prc.to_proc
  elsif prc.respond_to?(:call)
    prc.method(:call).to_proc
  else
    raise ArgumentError, "invalid value for Proc(): #{prc.inspect}"
  end
end

And with that we arrive at our final, robust, and flexible version of juxt:

def juxt(*lambdas)
  ->(*args) do
    lambdas.map {|l| Proc(l).(*args) }
  end
end

6.3 Dropping the Ampersands

One of the most common higher-order functions is Array#map. It only takes a single block parameter, nothing else. So if we want to pass it a lambda, we have to prefix it with an ampersand to make it clear we are passing it in as the block. Let’s fix that so Array#map can handle both cases.

class Array
  # First create an alias of the original map, so we can refer to it
  alias map_orig map

  # Now we can redefine map
  def map(lambda = nil, &block)
    map_orig(& block_given? ? block : Proc(lambda))
  end
end

# Look ma, no ampersands!
[1, 2, 3].map(:next)

We can do the same with each, select, and reject. We are unlikely to break existing code, since the existing version raises an error when called with a regular argument.

Since we are starting to repeat ourselves, let’s whip out some meta-programming to make our lifes easier.

class Module
  def autoblock(*args)
    args.each do |name|
      orig = "#{name}_orig"
      alias_method orig, name

      define_method name do |lambda = nil, &block|
        send(orig, & block ? block : Proc(lambda))
      end
    end
  end
end

This is not a book on meta-programming, so I won’t explain this snippet in detail, but if you squint you can see that it’s simply a generalization of what we did before. Now we can start marking methods as having an “autoblock”:

class Array
  autoblock :each, :reject, :select, :map
end

[1,2,3].reject {|x| x > 1} # => [1]
[1,2,3].map(:next) # => [2, 3, 4]

And of course we should use autoblock in our own code. From Ruby 2.1 onwards method definitions (def statements) return the name of the method being defined, so we can write:

# Ruby 2.0 and earlier
def my_method(&blk)
  # ...
end
# autoblock must be called after the method has been defined
autoblock :my_method

# Ruby 2.1 and later
autoblock def my_method(&blk)
  # ...
end

6.4 Function Composition

Functional programming really comes into its own when you can start “computing” functions. After all functions, lambdas, are really just values. While it might at first glance not make much sense to multiply and subtract one lambda from another, there sure are meaningful operations we can do to “calculate” new lambdas based on existing ones.

We will introduce a few operations by defining operators on Proc and Symbol. Again breaking existing code is unlikely, since these operators don’t exist in vanilla Ruby. However code might rely on these methods not being implemented. 6

module FunOperators
  def *(other)
    ->(*args) { self.to_proc.(other.to_proc.(*args)) }
  end
end

[Proc, Symbol].each {|klz| klz.send(:include, FunOperators) }

This * is the compose operator, it takes two lambdas, and pipes the output of one into the other. 7 You can read it as “comes after” or sometimes conveniently as “of the”.

For example, using the Twitter gem:

twitter.home_timeline.select(not(:empty?) * urls).map(
  :expanded_url * :first * :urls
)

This snippet selects all tweets in the users home timeline that have one or more urls in them. It then takes the epanded_url of the first of the urls.