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.

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: trueAvoid 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:Prefer Immutable Classes
Enabled: true
Lint/CheckForMethodsWithNoSideEffects:
Enabled: true
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 BankAccountAvoid Reassignment
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
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:
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.
By: Noah Matisoff
No comments:
Post a Comment