Embracing Functional Style in Ruby

The benefits of functional programming have become well-known and battle-tested, especially in recent years, and are being adopted in languages that aren’t touted as functional programming languages.
Specifically, in Ruby, there have been many great articles and talks on how to adopt functional style, and even how to mix it with object-oriented programming. Some insightful articles can be found below that will help with prefacing the rest of this article:
Functional Programming in Ruby — Brandon Weaver
Functional Programming in Ruby for people who don’t know what functional programming is — Amber Wilkie
Avoid Mutation — Functional Style in Ruby — Tom Dalling
… and many more!
The standard library even tends to default to methods that are functional over mutating, though does support mutating methods with the bang notation(mapvs. map! , for example). Below, I have outlined conventions, standards, and a styleguide for embracing functional style in your existing applications.
Adopting the Style
String Immutability
By default, strings are mutable in Ruby. This tended to result in lots of calls to Object#freeze on String objects. However, as of Ruby 2.3, a magic comment can be added to all files to make strings immutable:
# frozen_string_literal: true
Avoid Mutating Methods
Avoid mutating methods where possible, and adopt a preference for map, reduce, each_with_object, etc. Additionally, treat methods with a bang ( ! ) as a code smell, and carefully consider trade-offs.
This can be adopted by adding the following rules via Rubocop:
Lint/Void:
Enabled: true
Lint/CheckForMethodsWithNoSideEffects:
Enabled: true
Prefer Immutable Classes
Rather than mutating internal state in an object, adopt a preference for creating a new object with any would-be modified state. The classic example is a bank account object that exposes public APIs for withdraw(amount) and deposit(amount) . Rather than taking this approach, consider the below:
class BankAccount
attr_reader :balance def initialize(balance)
@balance = balance
end def withdraw(amount)
BankAccount.new(@balance - amount)
end def deposit(amount)
BankAccount.new(@balance + amount)
end
end
Avoid Reassignment
Reassigning any variables in your program is likely a smell that it’s imperative style and not using functional methods where it could be. Ruby does not have final variables (like Java) or const variables (like JavaScript), so be mindful of any reassignments and carefully weigh the trade-offs.
Avoid setters using helpers

As convenient as attr_writer and attr_accessor are, they’re exposing public APIs for mutating internal state in objects. attr_reader is still acceptable, but any “setter” methods should avoid mutating internal member variables.
Prefer Reusable, Curried Lambdas
Prefer reusable lambdas over defining logic and conditions manually. For example:
2.5.3 :030 > divisible_by = -> (x, y) { (y % x).zero? }.curry
=> #<Proc:0x00007f93671713b0 (lambda)>
2.5.3 :031 > (1..10).select(&divisible_by.(3))
=> [3, 6, 9]
As opposed to rewriting the same Enumerable#select with different arguments in each case in your codebase:
foo.rb(1..10).select { |num| (num % 3).zero? }bar.rb(1..10).select { |num| (num % 5).zero? }
These are simple examples, but can be applied to any realistic data processing, filtering, or mapping to your domain or context.
Conclusion
Adopting and embracing functional style in Ruby can empower the developer, but more importantly:
  • Typically is less error prone.
  • Makes it easier to grok data flow and onboard developers.
  • Avoids mixture of non-functional and functional style in your Ruby projects.
So next time you’re writing in Ruby, consider the trade-offs of functional style and make conscious decisions while enjoying the expression and options that are available in this great language!
Post a Comment