FuncTional Programming
ON RAILS
Functional / OO
name = "Robert"
big_name = String.upcase(name)
IO.puts(name) # => "Robert"
IO.puts(big_name) # => "ROBERT"
name = "Robert"
big_name = name.upcase
puts(name) # => "Robert"
puts(big_name) # => "ROBERT"
date_format = "%m/%d"
date_format_with_year = date_format << "/%Y"
puts(date_format_with_year) # => "%m/%d/%Y"
puts(date_format) # => "%m/%d/%Y"
# 😢
a = [1, 2, 3]
b = a
b.push(4)
a.push(5)
a # => [1, 2, 3, 4, 5]
b # => [1, 2, 3, 4, 5]
OOP IS GREAT
- Colocation of "material" and "tools"
- Clear boundaries between entities
- Powerful mental model of noun → class
- Built-in, easy state management
FP IS GREAT
- Immutable values are hard to screw up
- Pure functions are hard to screw up
I Want IT all
- From OOP:
- Code organization
- Colocation of "material" and "tools"
- Straightforward state management
- From FP:
- Immutable values
- Pure Functions
VALUE OBject
# Given some event times,
# Find the check-ins for those event times
class Event::AttendanceChart::ChartBar
include HasCheckInCounts
def initialize(event_times)
@event_times = event_times
end
def check_ins
@check_ins ||= CheckIn
.includes(:check_in_times)
.where("check_in_times.event_time_id" => @event_times.pluck(:id))
end
end
Public API is immutable data
Function OBject
class Location::FilterSentence::MaxMinFilterString
def initialize(stringify_value:)
@stringify_value = stringify_value
end
def comparison_string(min, max)
if min.present? && max.present?
if min == max
value_string(min)
else
"#{value_string(min)} to #{value_string(max)}"
end
elsif min.present?
"#{value_string(min)} and Up"
elsif max.present?
"Up to #{value_string(max)}"
else
nil
end
end
private
def value_string(val)
@stringify_value.call(val)
end
end
Public API is pure functions
Object OBject
class Event::AttendanceChart::ChartRange
attr_reader :ends_at, :starts_at, :dates
def initialize(ends_at:, range:, period:)
@ends_at = ends_at
@starts_at = send("starts_at_#{range}", @ends_at)
date_range = (starts_at.to_date..@ends_at.to_date)
@dates = send("group_by_#{period}", date_range)
end
private
def starts_at_this_month(ends_at)
# ...
end
def starts_at_this_year(ends_at)
# ...
end
def group_by_day(range)
# ...
end
def group_by_week(range)
# ...
end
end
Public API is data,
methods are pure functions
Sources
"Using a Ruby Class To Write Functional Code", Pat Shaunessy
"A Functional Pattern System for Object-Oriented Design",
Thomas Kuhne
"Commanding Objects Toward Immutability", Jim Gay
FP on Rails
By Robert Mosolgo
FP on Rails
Apply functional concepts to object-oriented code
- 759