Functional Composition in Ruby

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

Anjali Jaiswal
Apr 19 · 5 min read
Photo by Valeria Bold on Unsplash

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 way
def first_odd(array)
for i in 0...array.size
break i if i.odd?
end
end

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 way
def 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 inject
sum = -> list { list.inject(&:+) }
# lambda for calculating average adoption fee
total_adoption_fee = -> dogs do
adoption_fee = dogs.map { |dog| dog.adoption_fees * dog.adopted }
sum[adoption_fee]
end

# lambda for calculating average adoption fee
avg_adoption_fee = -> dogs do
total_adoption_fee[dogs]/ total_adoptions[dogs]
end
# lambda for getting number of dogs adopted
total_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 breed
golden = -> 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
end
end
$ 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 }
end
end

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.

Sign up for Geek Culture Hits

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.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Geek Culture

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

Anjali Jaiswal

Written by

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).

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

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store