No Detail Too Small
Expression-oriented programming (also known as functional or side-effect-free programming, although the three things are related, not synonymous) is a wonderful way to make calculations easier to understand and maintain. However, sometimes deeply nested function calls or mixing function calls with method invocations can make an expression difficult to understand at a glance. Here is a tip for refactoring your expressions so they are easier to read.
Expressions naturally form a tree, with values at the leaves and function calls or method invocations at each node. In this post, I’ll be talking about the simplest form of expression, a
pipeline. A pipeline is an expression that does not branch: a value (or often collection of values) is transformed by two or more function calls or method invocations in succession. Here’s a slightly obfuscated example of a pipeline working with collections from one of
our Rails applications:
widget.fizz.fizz_buzzes.select { |fizz_buzz|
fizz_buzz.widgets_column_name =~ /^special_.*/
}.map { |fizz_buzz|
widget.attribute_present?(fizz_buzz.widgets_column_name) &&
{ fizz_buzz.label => widget.send(fizz_buzz.widgets_column_name) } ||
{}
}.inject({}, &:merge)
While the details don’t make much sense out of context, the overall pattern ought to be familiar as an example of the MapReduce pattern (without the distributed processing, of course).
Pipelines read from right-to-left, left-to-right, or both. For example, this set of three nested function calls reads from right-to-left:
sum_numbers.call(square_numbers.call(odd_numbers.call(1..100)))
If I try to read it from left-to-right, it’s sounds like a caricature of speech: “The sum of the squares of the odd numbers from one to one hundred.” You can’t figure it out unless you build an abstract syntax tree in your head and then evaluate it with a stack machine. Having to emulate a computer to figure out what something means is not a good sign. it reads much easier from right-to-left: “Take the numbers from one to one hundred. Select the odd ones. Square them. And finally, take the sum.”
Popular languages like Ruby make it easy to write expressions that read from left-to-right directly: here’s an example from Ruby 1.9 (or with Symbol#to_proc):
(1..100).select(&:odd?).map { |n| n*n }.inject(&:+)
=> 166650
Object orientation’s emphasis on nouns at the expense of verbs has its issues. But when a computation really is a step-wise transformation of data, I find that chaining methods makes code a lot easier to understand than nesting functions. On the other hand, I prefer nesting functions when the expression has more of a tree form.
But whichever direction you prefer, I find it very difficult to read code that mixes directions in the same expression:
square_elements = lambda { ... } # content elided
square_elements.call((1..100).select(&:odd?)).inject(&:+)
You go left-to-right to select odd members, then back left to square them, then right to sum them. I find this much more confusing than either the nested functions or the chained method calls.
Object#intoA little while ago, I saw John Carter define factorial in Ruby as a
method in the Integer class:
class Integer
def factorial
return 1 if self <= 1
self * (self-1).factorial
end
end
My first reaction was to think that adding factorial as a method was an idea from
another planet:
1 why should integers know how to answer their own factorials? This seemed like a classic case of
a function that should not be an object method. But nevertheless, having calculations be methods instead of functions lets you write a certain type of expression consistently from left-to-right (
5.succ.factorial.succ.odd
) instead of mixing directions (
factorial.call(5.succ).succ.odd?
).
All the same, there are good reasons why we don’t overload numeric classes with every possible calculation and formula. So what can we do? How about:
class Object
def into expr = nil
expr.nil? ? yield(self) : expr.to_proc.call(self)
end
end
Now, snarfing
Charles Duan’s code, we can write:
y = proc { |generator|
proc { |x|
proc { |*args|
generator.call(x.call(x)).call(*args)
}
}.call(proc { |x|
proc { |*args|
generator.call(x.call(x)).call(*args)
}
})
}
factorial = y.call(proc { |callback|
proc { |arg|
if arg.zero? then 1
else arg * callback.call(arg - 1)
end
}
})
Which lets us write:
5.succ.into(factorial).succ.odd?
=> true
I read this as “Start with five, get its successor, put that into the factorial proc, take the result’s successor, and answer whether it is odd.” The whole thing reads in one consistent style, you aren’t mixing left-to-right method chaining with right-to-left nesting functions. I wouldn’t go crazy with Object#into in a program, but if you have an expression that is predominately chaining methods, Object#into can make it consistent and improve its readability.
Function CompositionThere is more than one way to skin a cat. If
f(g(h(value)))
is too constricting, we can
compose functions instead of nesting them. So we can write:
class Proc
def self.compose(f, g)
lambda { |*args| f[g[*args]] }
end
def *(g) # Tom's origional composition operator
Proc.compose(self, g)
end
def |(g) # The reverse composition operator, mimicing a pipe
Proc.compose(g, self)
end
end
plus1 = lambda { |n| n + 1 }
squared = lambda { |n| n * n }
minus1 = lambda { |n| n - 1 }
This allows us to write
(minus1 | squared | plus1).call(5)
, which puts
almost everything in left-to-right order. Hey, remember Object#into? Why don’t we try it?
5.into(minus1 | squared | plus1)
That saves us from writing
5.into(minus1).into(squared).into(plus1)
if we find three instances of “into” a little noisy. Composing functions using
*
lets us maintain right-to-left order and composing functions with
|
lets us create left-to-right order when we are making a “pipeline” of expressions.
SummaryIn the end, this is a very trivial idea: When an expression can be written so that it reads consistently from left-to-right or consistently from right-to-left, do so. The code will be easier to read.
- Uh, yes, I am familiar with Smalltalk. I’m thinking that my opinion of my ability to make a joke far exceeds my actual ability: the phrase is meant as a pun on Edgar Rice Burroughs’s Barsoomian Tales, featuring the Warlord John Carter. But all that being said, regardless of how OO you want to get, I am not convinced that objects are responsible for every operation that can possibly be performed on them.