Does Ruby has function composition?

Hercules Lemke Merscher - Sep 26 '19 - - Dev Community

Introduction

What's function composition? Why it matters?

In functional programming languages, such as Haskell, we are able to define functions and compose them to create new functions, combining its behaviours. For example:

fizzBuzz n = mod n 3 == 0 || mod n 5 == 0

fizzBuzzSum = sum . filter fizzBuzz

main = putStrLn $ show (fizzBuzzSum [1..999])
-- 233168
Enter fullscreen mode Exit fullscreen mode

When we want to combine 2 or more functions, we can use the . operator in Haskell to combine functions. What happens is the value will be chained through one function to the other(s), resulting into a value after every function is applied.

This possibility give us a degree of flexibility, as we are able to define new functions re-using previous defined functions to create a new one. Thumbs up for productivity!

What about Ruby?

Ruby is multi-paradigm language. It's true that it includes some functional features, but it doesn't have high order functions. Well, at least not as we are used to in Haskell or Javascript.

Ruby has lambdas and Proc objects. They are interchangeable in many cases:

numbers = (1..999).to_a

fizz_buzz = -> (n) { n % 3 == 0 || n % 5 == 0 }

filter = -> (fn, array) { array.select(&fn) }

sum = proc { |array| array.reduce(:+) }

puts sum.call(filter.call(fizz_buzz, numbers))
# 233168
Enter fullscreen mode Exit fullscreen mode

If you pay attention, fizz_buzz is declared as a lambda, while sum is declared as a Proc object. Under the hood they are both represented as a Proc. So far so good.

What if you want to compose them? If you're using Ruby 2.6+ it is pretty easy:

fizz_buzz_sum = sum << filter.curry[fizz_buzz]

# composing the other way around
fizz_buzz_sum = filter.curry[fizz_buzz] >> sum

puts fizz_buzz_sum.call(numbers)
# 233168
Enter fullscreen mode Exit fullscreen mode

Not bad at all! Thanks Ruby core team for the >> and << operators for Proc. :)

But Ruby is an object oriented programming language at its core. Should we ditch classes and objects in favour of using lambdas everywhere?

Object oriented... Object oriented everywhere!

In object oriented languages, classes, objects and methods are used to represent behaviours instead of functions. This is the way we use Ruby on a daily basis, cause in Ruby everything is an object, remember?

class FizzBuzz
  def call(n)
    n % 3 == 0 || n % 5 == 0
  end
end

puts FizzBuzz.new.call(1)
# false
puts FizzBuzz.new.call(15)
# true
puts FizzBuzz.new.call(30)
# true
Enter fullscreen mode Exit fullscreen mode

It achieves the same but, how to compose objects with lambdas in a seamless way?

The to_proc is a protocol for converting an object to a Proc object:

class FizzBuzz
  def to_proc
    -> (n) { n % 3 == 0 || n % 5 == 0 }
  end
end

puts FizzBuzz.new.to_proc.call(3)
# true
puts FizzBuzz.new.call(3)
# true
Enter fullscreen mode Exit fullscreen mode

Isn't it cool?!

puts (filter.curry[FizzBuzz.new] >> sum).call(numbers)
# 233168
Enter fullscreen mode Exit fullscreen mode

This way we can even mix objects and lambdas.

An alternative way to compose

Well, on Ruby 2.6+ we have the >> and << operators to compose lambdas. But what about previous versions of Ruby?

The beauty of functional programming:

sum_fizz_buzz = -> (value) do
  [filter.curry[FizzBuzz.new], sum].reduce(value) do |previous_result, object|
    object.to_proc.call(previous_result)
  end
end

puts sum_fizz_buzz.call(numbers)
# 233168
Enter fullscreen mode Exit fullscreen mode

Almost everything can be solved just with... functions. :)

References

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .