Lots of patterns are encountered in large ruby codebases which can be expressed more elegantly in a functional manner. Ruby provides a number of built-in facilities to enable functional style programming.
This talk aims to provide a jumping-off point for rubyists who are used to imperative style programming and mutable state a jumping-off point for exploring functional paradigms.
3. 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) }
4. 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)
6. 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
7. 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"
8. 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!"
10. 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
11. 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
12. 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) }
14. MAP
Solution: Overwrite the original list
# Square all of our numbers
(1...numbers.length).each do |index|
numbers[index] **= 2
end
15. 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
16. 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(&:**)
18. 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
19. 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(&:*)
21. 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
22. 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]]
29. 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.
30. 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
31. 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
32. 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)
33. 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