FUNCTIONALRUBY
FIRST-CLASSFUNCTIONS
You already use these
# Print each number from 1-30 in hexadecimal
(1..30).each do |number|
puts number.to_s(16)
end
FIRST-CLASSFUNCTIONS
Changing the example slightly:
# Create a lambda which prints a number in hexadecimal
puts_in_hex = lambda do |number|
puts number.to_s(16)
end
(1..10).each { |i| puts_in_hex.call(i) }
FIRST-CLASSFUNCTIONS
This can be further simplified:
puts_in_hex = lambda do |number|
puts number.to_s(16)
end
(1..10).each(&puts_in_hex)
ASIDE:THEAMPERSAND
The ampersand indicates that we're working with a block
def use_block(&block)
block.call(2)
end
use_block do |number|
number * 2
end
# => 4
ASIDE:THEAMPERSAND
It also causes a viariable to be interpreted as a block.
def use_block(&block)
block.call(2)
end
multiply_two = lambda do |number|
number * 2
end
use_block(&multiply_two)
# => 4
ASIDE:SYMBOL#TO_PROC
Creates a proc which will call a method on an object
call_to_s = :to_s.to_proc
# Looks something like this...
proc do |obj, *args|
obj.send(:to_s, *args)
end
# Ends up being 10.send(:to_s) or 10.to_s
call_to_s.call(10)
# => "10"
# Ends up being 10.send(:to_s, 16) or 10.to_s(16)
call_to_s.call(10, 16)
# => "a"
ASIDE:SYMBOL#TO_PROC
In a method call, &:sym is a shortcut for :sym.to_proc
def apply_block_to_array_and_print(&block)
yield ['h', 'e', 'll', 'o', ' ', 'fun', 'ctions', '!']
end
apply_block_to_array_and_print(&:join)
# => "hello functions!"
FILTER
Problem: A list needs to be filtered
FILTER
Solution: delete everything else
# Find all of the adverbs in a word list
word_list.each do |item|
word_list.delete(item) unless /ly$/.match(item)
end
FILTER
Better solution: build a new list!
# Find all of the adverbs in a word list
adverbs = []
word_list.each do |item|
adverbs << item if /ly$/.match(item)
end
FILTER
Better yet: use Enumerable#select
# Find all of the adverbs and non-adverbs in a word list
adverbs = word_list.select { |item| /ly$/.match(item) }
not_adverbs = word_list.reject { |item| /ly$/.match(item) }
MAP
Problem: A list needs to have elements modified
MAP
Solution: Overwrite the original list
# Square all of our numbers
(1...numbers.length).each do |index|
numbers[index] **= 2
end
MAP
Better Solution: generate a new list
numbers = [1,2,3,4,5]
squares = []
# Square all of the numbers
numbers.each do |number|
squares << number ** 2
end
MAP
Better yet: use Enumerable#map
numbers = [1, 2, 3, 4, 5]
# Square all of our numbers
squares = numbers.map { |number| number ** 2 }
# Another way we could do it
squares = numbers.each_with_object(2).map(&:**)
REDUCE
Problem: A list needs to be transformed
REDUCE
Solution: Iterate through the list
numbers = [1, 2, 3, 4, 5]
product = 1
# Alter the product iteratively
numbers.each do |number|
product *= number
end
REDUCE
Better solution: Use Enumerable#reduce
numbers = [1, 2, 3, 4, 5]
# Calculate the product of the list members
product = numbers.reduce { |acc,item| acc * item }
# Shorter way to do the same
product = numbers.reduce(&:*)
ZIP
Problem: Two lists need to be intertwined
ZIP
Solution: Overwrite one of the lists
a = [1, 2, 3]
b = [4, 5, 6]
# Intertwine list a with list b
a.each_with_index do |number, index|
a[index] = [number, b[index]]
end
ZIP
Better Solution: use Enumerable#zip
a = [1, 2, 3]
b = [4, 5, 6]
# Intertwine list a with list b
c = a.zip(b)
# => [[1, 4], [2, 5], [3, 6]]
WARNING
Religion ahead!
STATE
Ruby is good at state.
Mutable
Implicit
Hidden
DANGEROUSSTATE
Consider:
given_names = %w(Alice Bob Eve Mallory)
short_names = given_names.select { |name| name.length < 5 }
short_names.each { |name| puts name.upcase! }
given_names[1] # => ???
DANGEROUSSTATE
Consider:
given_names = %w(Alice Bob Eve Mallory)
short_names = given_names.select { |name| name.length < 5 }
short_names.each { |name| puts name.upcase! }
given_names[1] # => "BOB"
DUPTOTHERESCUE?
Maybe Object#dup will help?:
given_names = %w(Alice Bob Eve Mallory)
safe_names = given_names.dup
short_names = safe_names.select { |name| name.length < 5 }
short_names.each { |name| puts name.upcase! }
given_names[1] # => ???
DUPTOTHERESCUE?
Maybe Object#dup will help?:
given_names = %w(Alice Bob Eve Mallory)
safe_names = given_names.dup
short_names = safe_names.select { |name| name.length < 5 }
short_names.each { |name| puts name.upcase! }
given_names[1] # => "BOB"
WELP.
State is difficult to manage and track
Particularly as systems grow in complexity
Things get more difficult with real threads (Rubinius, JRuby)
Avoiding mutable state in most cases avoids this problem.
BATTLINGSTATE
Avoiding state fits well with good style:
Keep methods short and responsible for one thing
Write methods with idempotence in mind
When mutations seem necessary, use more functions
RULES:MADETOBEBROKEN
Ruby exposes state to the programmer in a dangerous way
Once concurrency comes into play, scary dragons emerge
Avoiding mutable state helps, but can be expensive
PAINPOINTS
Sometimes avoiding state doesn't make sense:
Code runs much heavier than it could
Code runs much slower than it otherwise might (GC runs)
PAINMANAGEMENT
We can keep things from getting out of hand!
Keep code which has side-effects to a minimum
Isolate code which produces side-effects
Don't make it easy to mutate state accidentally
QUESTIONS?COMMENTS?

A Gentle Introduction to Functional Paradigms in Ruby

  • 1.
  • 2.
    FIRST-CLASSFUNCTIONS You already usethese # Print each number from 1-30 in hexadecimal (1..30).each do |number| puts number.to_s(16) end
  • 3.
    FIRST-CLASSFUNCTIONS Changing the exampleslightly: # Create a lambda which prints a number in hexadecimal puts_in_hex = lambda do |number| puts number.to_s(16) end (1..10).each { |i| puts_in_hex.call(i) }
  • 4.
    FIRST-CLASSFUNCTIONS This can befurther simplified: puts_in_hex = lambda do |number| puts number.to_s(16) end (1..10).each(&puts_in_hex)
  • 5.
    ASIDE:THEAMPERSAND The ampersand indicatesthat we're working with a block def use_block(&block) block.call(2) end use_block do |number| number * 2 end # => 4
  • 6.
    ASIDE:THEAMPERSAND It also causesa viariable to be interpreted as a block. def use_block(&block) block.call(2) end multiply_two = lambda do |number| number * 2 end use_block(&multiply_two) # => 4
  • 7.
    ASIDE:SYMBOL#TO_PROC Creates a procwhich will call a method on an object call_to_s = :to_s.to_proc # Looks something like this... proc do |obj, *args| obj.send(:to_s, *args) end # Ends up being 10.send(:to_s) or 10.to_s call_to_s.call(10) # => "10" # Ends up being 10.send(:to_s, 16) or 10.to_s(16) call_to_s.call(10, 16) # => "a"
  • 8.
    ASIDE:SYMBOL#TO_PROC In a methodcall, &:sym is a shortcut for :sym.to_proc def apply_block_to_array_and_print(&block) yield ['h', 'e', 'll', 'o', ' ', 'fun', 'ctions', '!'] end apply_block_to_array_and_print(&:join) # => "hello functions!"
  • 9.
    FILTER Problem: A listneeds to be filtered
  • 10.
    FILTER Solution: delete everythingelse # Find all of the adverbs in a word list word_list.each do |item| word_list.delete(item) unless /ly$/.match(item) end
  • 11.
    FILTER Better solution: builda new list! # Find all of the adverbs in a word list adverbs = [] word_list.each do |item| adverbs << item if /ly$/.match(item) end
  • 12.
    FILTER Better yet: useEnumerable#select # Find all of the adverbs and non-adverbs in a word list adverbs = word_list.select { |item| /ly$/.match(item) } not_adverbs = word_list.reject { |item| /ly$/.match(item) }
  • 13.
    MAP Problem: A listneeds to have elements modified
  • 14.
    MAP Solution: Overwrite theoriginal list # Square all of our numbers (1...numbers.length).each do |index| numbers[index] **= 2 end
  • 15.
    MAP Better Solution: generatea new list numbers = [1,2,3,4,5] squares = [] # Square all of the numbers numbers.each do |number| squares << number ** 2 end
  • 16.
    MAP Better yet: useEnumerable#map numbers = [1, 2, 3, 4, 5] # Square all of our numbers squares = numbers.map { |number| number ** 2 } # Another way we could do it squares = numbers.each_with_object(2).map(&:**)
  • 17.
    REDUCE Problem: A listneeds to be transformed
  • 18.
    REDUCE Solution: Iterate throughthe list numbers = [1, 2, 3, 4, 5] product = 1 # Alter the product iteratively numbers.each do |number| product *= number end
  • 19.
    REDUCE Better solution: UseEnumerable#reduce numbers = [1, 2, 3, 4, 5] # Calculate the product of the list members product = numbers.reduce { |acc,item| acc * item } # Shorter way to do the same product = numbers.reduce(&:*)
  • 20.
    ZIP Problem: Two listsneed to be intertwined
  • 21.
    ZIP Solution: Overwrite oneof the lists a = [1, 2, 3] b = [4, 5, 6] # Intertwine list a with list b a.each_with_index do |number, index| a[index] = [number, b[index]] end
  • 22.
    ZIP Better Solution: useEnumerable#zip a = [1, 2, 3] b = [4, 5, 6] # Intertwine list a with list b c = a.zip(b) # => [[1, 4], [2, 5], [3, 6]]
  • 23.
  • 24.
    STATE Ruby is goodat state. Mutable Implicit Hidden
  • 25.
    DANGEROUSSTATE Consider: given_names = %w(AliceBob Eve Mallory) short_names = given_names.select { |name| name.length < 5 } short_names.each { |name| puts name.upcase! } given_names[1] # => ???
  • 26.
    DANGEROUSSTATE Consider: given_names = %w(AliceBob Eve Mallory) short_names = given_names.select { |name| name.length < 5 } short_names.each { |name| puts name.upcase! } given_names[1] # => "BOB"
  • 27.
    DUPTOTHERESCUE? Maybe Object#dup willhelp?: given_names = %w(Alice Bob Eve Mallory) safe_names = given_names.dup short_names = safe_names.select { |name| name.length < 5 } short_names.each { |name| puts name.upcase! } given_names[1] # => ???
  • 28.
    DUPTOTHERESCUE? Maybe Object#dup willhelp?: given_names = %w(Alice Bob Eve Mallory) safe_names = given_names.dup short_names = safe_names.select { |name| name.length < 5 } short_names.each { |name| puts name.upcase! } given_names[1] # => "BOB"
  • 29.
    WELP. State is difficultto manage and track Particularly as systems grow in complexity Things get more difficult with real threads (Rubinius, JRuby) Avoiding mutable state in most cases avoids this problem.
  • 30.
    BATTLINGSTATE Avoiding state fitswell with good style: Keep methods short and responsible for one thing Write methods with idempotence in mind When mutations seem necessary, use more functions
  • 31.
    RULES:MADETOBEBROKEN Ruby exposes stateto the programmer in a dangerous way Once concurrency comes into play, scary dragons emerge Avoiding mutable state helps, but can be expensive
  • 32.
    PAINPOINTS Sometimes avoiding statedoesn't make sense: Code runs much heavier than it could Code runs much slower than it otherwise might (GC runs)
  • 33.
    PAINMANAGEMENT We can keepthings from getting out of hand! Keep code which has side-effects to a minimum Isolate code which produces side-effects Don't make it easy to mutate state accidentally
  • 34.