Ruby: functional programming
In one of my previous jobs I worked as a full stack engineer on a codebase with a Ruby backend and a Javascript/React frontend. Having used Clojure a fair bit in my spare time I was keen to code in a functional style. At first glance this seems tricky in Ruby as it doesn't have first class functions.
Lambda
Ruby might not have first class functions but it does have lambda
.
We can assign a lanbda
to a value:
add = lambda {|x, y| x + y}
We can return a lambda
from a function:
def identity (x)
x
end
identity(add)
=> add lambda
We can store a lambda
in a data structure:
a = [add]
a.first.call(4, 6)
=> 10
Working with lambda
looks something like this:
add = lambda {|x, y| x + y}
identity = lambda {|x| x}
identity.call(add).call(1, 2)
=> 3
We can make those calls to call
less verbose by using the .()
shorthand:
add = lambda {|x, y| x + y}
identity = lambda {|x| x}
identity.(add).(1, 2)
=> 3
If writing lambda
seems like a lot of work, we can use the ->
shorthand:
add = -> x,y {x + y}
identity = -> x {x}
identity.(add).(1, 2)
=> 3
We can use a lambda
as a higher order parameter with &
operator:
inc = -> x {x + 1}
[1, 2, 3, 4].map(&inc)
=> [2, 3, 4, 5]
We can pass methods as functions using &
combined with the symbol of the method, in this case :even?
:
[1, 2, 3, 4].(&:even?).reduce(:+)
=> 6
Currying
lambda
even has built in currying:
add = -> x,y {x + y}
inc = add.curry.(1)
[1, 2, 3, 4].map(&inc).reduce(:+)
=> 14
Compose & Pipe
It's really useful to be able to compose functions. We can do this in Ruby by writing our own comp
function with reduce. This will compose functions right to left:
add = -> x,y {x + y}
times = -> x,y {x * y}
comp = -> *fns {fns.reduce {|f, g| -> x {f.(g.(x))}}}
[1,2,3,4].map(&comp.(add.curry.(1), times.curry.(2)))
=> [3, 5, 7, 9]
If we want to compose left to right we can write our own pipe
function:
pipe = -> *fns {fns.reverse.reduce {|f, g| -> x {f.(g.(x))}}}
[1,2,3,4].map(&pipe.(add.curry.(1), times.curry.(2)))
=> [4, 6, 8, 10]
Or since ruby 2.6
we can use <<
or >>
:
add = -> x,y {x + y}
times = -> x,y {x * y}
add_one_and_times_by_two = add.curry.(1) >> times.curry.(2)
add_one_and_times_by_two.(1)
=> 4
times_by_two_and_add_one = add.curry.(1) << times.curry.(2)
times_by_two_and_add_one.(1)
=> 3
We can also do inline composition:
add = -> x,y {x + y}
times = -> x,y {x * y}
[1,2,3,4].map(&(add.curry.(1) >> times.curry.(2) >> add.curry.(5)))
=> [9, 11, 13, 15]
Most of these features* have been in Ruby since or before Ruby 1.9
which was released in 2007. It turns out functional programming has been part of Ruby for a long time and might be a lot more idiomatic than you first think.
* Except for <<
and >>
which were added in Ruby 2.6
released in 2018.