Functional Composition in Ruby

Let’s explore functional thinking and what it entails wrt Ruby.

Apr 19 · 5 min read

We all know that Ruby is an Object-oriented language, and while that’s true we can still use Ruby in a purely functional way. It won’t be much Rubish if we did that though, so let’s see what bits we can do with Ruby that are inspired from functional programming.

Functional programming is essentially like writing a mathematical program. Let’s understand this with an example, consider that I want to find out first odd number in an array. Now we will write two functions with same objective, one in Ruby way, another in a functional way.

`# Ruby waydef first_odd(array)  for i in 0...array.size    break i if i.odd?  endend`

In Ruby function we are reassigning value of i with each iteration of the loop, which would not be possible in a mathematical function, because there we define a variable, it’s the definition and not an assignment. That’s the major difference between functional and non-functional programming, functional programming stresses on immutability and referential transperancy(meaning if you pass same value to a function again and again the output will always be same.) Now, let’s see how we can write the same method in a functional way:

`# Functional waydef first_odd(array)  i = array.shift   return nil if array.empty?  if i.odd?    return i  else    first_odd(array)   end end`

In Functional way we wrote a recursive program, which is like a mathematical function ie it checked if the first element is odd and if not it called itself with remaining elements. From this we can derive three important points about functional programming which we need to remember:

1. Functions are definitions, not list of instructions.
2. We don’t assign variables, we define things.
3. We can pass functions in another function, just like a variable.

Let’s dive more into how functional composition can rescue us in daily life problems. Suppose we have a dog adoption application and we have been asked for a report on how well the dogs are getting adopted. You already have a list of dogs and their attributes.

`# We have:\$ dogs.sample=> #<struct Dog adoption_fees: 12, rating: 4, breed: :golden, adopted: 22>`

First thing we can ask for in a report is pretty simple, just average adoption fees. To do this we can create a Report class, and then add an inititalize method to take and store a list of all the dogs and then add a run method to calculate the average adoption fees.

`class Report  def initialize(dogs)    @dogs = dogs  end  # inject is an enumerable function  def run    money_taken = @dogs.inject(0) do |total, dog|       (dog.adoption_fees * dog.adopted) + total    end        total_adoption =  @dogs.inject(0) do |total, dog|       dog.adopted + total    end        money_taken / total_adoption  end end\$ Report.new(dogs).run=> 20`

Recently a lot of Golden’s got adopted because of COVID, and now we need a new report to show the average adoption fees the organisation got from it. We can tweak the previous code to add a new parameter which selects only a given breed and do calculations. Next we require top three breeds who are getting adopted. These request will make our Report class long and as new requests will come, this approach will become harder to maintain and will introduce a lot of flags and conditional logic. There are tons of ways wherein we can refactor such case, but since we are looking at functional composition lets see what can be done here with some of Ruby’s functional programming ideas.

# Note: This section will use lambdas heavily, so if you need a revision: Ruby Blocks, Procs and Lambdas

`# lambda for summing up using injectsum = -> list { list.inject(&:+) } # lambda for calculating average adoption feetotal_adoption_fee = -> dogs do  adoption_fee = dogs.map { |dog| dog.adoption_fees * dog.adopted }  sum[adoption_fee]end # lambda for calculating average adoption feeavg_adoption_fee = -> dogs do  total_adoption_fee[dogs]/ total_adoptions[dogs]end# lambda for getting number of dogs adoptedtotal_adoption = dogs { sum[dogs.map(&:adopted)] }\$ avg_adoption_fee[DOGS] => 20# now, we need to get adoption fees only for golden's# hence we will add a new lambda for getting jut one breedgolden = -> dogs { dogs.select { |dog| dog.breed == :golden }}`

Let’s rewrite our Reports class where we can use the lambdas we just created.

`class FunctionalReport   # splated argument will take a list of functions generated at  # each stage of report  def initialize(dogs, *fns)    @dogs = dogs    @fns = fns  end  def run    @fns.inject(@dogs) do |last_result, fn|      fn[last_result]    end  endend\$ FunctionalReport.new(DOGS, avg_adoption_fee).run=> 20\$ FunctionalReport.new(DOGS, golden, avg_adoption_fee).run=> 12# We can create more lambdas and work accordingly, lets say someone # wants to know adoption fees of dogs which have rating of three and # higherhigh_rating = -> dogs { dogs.select { |d| d.rating >= 3 }}\$ FunctionalReport.new(DOGS, high_rating, avg_adoption_fee).run=> 15`

As seen above, we can safely deduce with the use of functional programming:

1. We can add new features without modifying existing code
2. Create each step of report with very little code
3. No need to support unrelated code from other steps

But the thing is this is not very Rubish, and ignores a lot expressiveness and power that Ruby has to offer. So, what things we can use from Functional Programming in Ruby?

Now the takeaway from this article should be the approach, we wrote lambdas that implemented some small specific tasks and found a way of making them work together. We can do the same thing with objects implementing small specific rules and finding a way to compose them together.

`class BreedFilter  def initialize(breed)    @breed = breed  end  def apply(dogs)    dogs.select { |dog| dog.breed == @breed }  endend`

This Object-Oriented style of coding is much easier to read and think, feels much more natural while writing Ruby and most importantly it treats data as being immutable. Functional Programming has a lot to teach us, we don’t have to write purely functional code to reap the benefits. Hope this helped you in understanding how you can write better code. Sayonara for now, will catch you later with new tidbits. Stay happy, stay safe.

Geek Culture

Proud to geek out.

By Geek Culture

Subscribe to receive top 10 most read stories of Geek Culture — delivered straight into your inbox, once a week. Take a look.

Medium sent you an email at to complete your subscription.

Geek Culture

A new tech publication by Start it up (https://medium.com/swlh).

Written by

Anjali Jaiswal

Programmer, avid Reader, artist, exploring different trades, fun-loving, adventurous and most of the time a hell-yes person!

Geek Culture

A new tech publication by Start it up (https://medium.com/swlh).

Parallel, distributed and Concurrent Systems

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app