Lambdas and Enumerables in Ruby
I recently fell in love with a simple higher order function pattern in JavaScript. It goes a little something like this:
Given an array,
// .jsconst oneToTen = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
this:
oneToTen.map(number => number * 3)
//=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]
is the same as this:
const timesThree = number => number * 3oneToTen.map(timesThree)
//=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]
This enterprise-grade example is not super complex, but you can imagine how useful this can be for readability and separation of concerns as blocks grow in line length. The same pattern works for other methods like Array.prototype.reduce
and Array.prototype.filter
.
After some travels to JavaScriptopolis and returning to Rubyland, I found myself wanting to employ a similar pattern. Since methods are not first class objects in Ruby, it’s not as straightforward. But I thought back to my early Ruby days to when I stumbled across the ideas of procs and lambdas, and that they had to do with executing blocks of code.
I avoided them at the time, as they seemed far to complex at the time, but they seemed more palatable this time around. For context:
To me, that means proc objects are the Ruby way to pass functions are around as objects. Lambdas are special kinds of procs. There differences are well documented here, but are beyond the scope of this post.
Fun fact: proc is short for procedure.
Using lambdas, here’s what I came up with for following the JavaScript pattern above:
Given an array,
# .rbone_to_ten = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
this:
one_to_ten.map { |number| number * 3 }
#=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]
is the same as this:
def times_three
lambda do |number|
number * 3
end
endone_to_ten.map(×_three)
#=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]
which is also the same as this (“stabby lambda” syntax):
def times_three
-> number { number * 3 }
endone_to_ten.map(×_three)
#=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]
and the lambda can also be stored in a plain ol’ variable:
times_three = -> number { number * 3 }one_to_ten.map(×_three)
#=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]
Lovely. Our .map
function calls a lambda in a very declarative style, and the lambda takes care of the imperative part.
What about arguments?
Imagine another enterprise case where you want to a function that tells you whether all words are greater than a given numerical argument:
words = ["apple", "okay", "not", "yes", "forgettable"]def longer_than(number)
-> word { word.length > number }
endwords.all?(&longer_than(5))
#=> falsewords.all?(&longer_than(2))
#=> true
Since we have the longer_than
lambda defined, we can use it on any other enumerable:
words.select(&longer_than(3))
#=> ["apple", "okay", "forgettable"]words.reject(&longer_than(3))
#=> ["not", "yes"]words.one?(&longer_than(5))
#=> true
Revisiting times_three
:
To make our times_three
lambda from before even more distilled and dynamic, we can do this:
def times(factor)
-> number { number * factor }
endone_to_ten.map(×(3))
#=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]one_to_ten.map(×(5))
#=>[ 5, 10, 15, 20, 25, 30, 35, 40, 45, 50 ]
Better yet:
def operate(sign, arg)
-> number { number.send(sign, arg) }
endone_to_ten.map(&operate(:*, 3))
#=>[ 3, 6, 9, 12, 15, 18, 21, 24, 27, 30 ]one_to_ten.map(&operate(:-, 6))
#=> [ -5, -4, -3, -2, -1, 0, 1, 2, 3, 4 ]
Hooray! Higher order functions that takes a lambda (or a special proc) as an argument, and that argument can take arguments. Just a second…
…okay I’m back. I had to raise my arms victoriously for writing such beautifully readable code.
Conclusion
Given these examples, I hope you find lambdas to be as sensible and desirable and BEAUTIFUL as I now do. You can deconstruct your enumerable calls to be more declarative, leaving the lambda to handle the actual implementation. So the next time you find yourself typing your enumerable’s do |var|
or { |var| ...
, throw a lambda in there.