How To Leverage Ruby's Functional Programming Capability

Ruby is a multi-paradigm programming language. It fully allows writing old-fashioned procedural code, but also provides many useful constructs and features from the functional world.

The majority of developers come to Ruby from the imperative world. They are used to making many local variables, changing their state and relying on implicit dependencies. Very quickly, it becomes clear that code can be much more expressive, using powerful idioms from functional languages. Ruby isn't a fully functional language by any means; functions are not first class citizens, evaluation flow is not lazy, pattern matching support is very limited, etc. But still, it is possible to write code in a functional way and garner many benefits as a result.

I'd like to start with a practical example. Let’s define a problem, try to solve it using both imperative and functional styles in Ruby and see what happens. Let me first point out that it’s very hard to come up with a good example that is concise and easily understandable and, at the same time, not too artificial, but I have done my best.

Problem definition: Write a function, which accepts a list of users with a full_name property and returns a string with users' names and birthdays sorted by distance from the current point in time. Birthdays of users can be obtained from an external system by using birthday method of BirthdayRegistry class. If multiple people are lucky enough to be born on the same day, the function should combine them together with a comma.

Example: Bob and Joe are both born on July 16, 1985, Maria celebrates her birthday on January 2, 1989, and Alice blows out her candles on October 25, 1989. The function should return "[Alice] - 1989-10-25; [Maria] - 1989-01-02; [Bob, Joe] - 1985-07-16" string.

Imperative implementation:

def birthday_sequence(users)
  result = ''
  hash = {}
  users.each do |user|
    birthday = BirthdayRegistry.birthday(:date, user)
    hash[birthday] ||= []
    hash[birthday] << user
  end
 
  sorted = hash.sort_by { |birthday, _| (Date.today - birthday).abs }
 
  sorted.each do |birthday, celebrators|
    result << '['
    names = []
    celebrators.each { |user| names << user.full_name }
    names.sort!
    names[0..-2].each do |name|
      result << name
      result << ', '
    end
    result << names.last + "] - #{birthday}; "
  end
  result[0..-3]
end

As I mentioned earlier, this example is a bit artificial, but code similar to this can be easily found in an arbitrary Ruby project featuring several junior developers. My hope is that you, as the reader, will practice some patience with this example since it is only used for demonstration purposes of the article.

Alex Shestakov

Comments