34
FUNCTIONAL RUBY

A Gentle Introduction to Functional Paradigms in Ruby

Embed Size (px)

DESCRIPTION

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.

Citation preview

Page 1: A Gentle Introduction to Functional Paradigms in Ruby

FUNCTIONAL RUBY

Page 2: A Gentle Introduction to Functional Paradigms in Ruby

FIRST-CLASS FUNCTIONS

You already use these# Print each number from 1-30 in hexadecimal(1..30).each do |number| puts number.to_s(16)end

Page 3: A Gentle Introduction to Functional Paradigms in Ruby

FIRST-CLASS FUNCTIONS

Changing the example slightly:# Create a lambda which prints a number in hexadecimalputs_in_hex = lambda do |number| puts number.to_s(16)end

(1..10).each { |i| puts_in_hex.call(i) }

Page 4: A Gentle Introduction to Functional Paradigms in Ruby

FIRST-CLASS FUNCTIONS

This can be further simplified:puts_in_hex = lambda do |number| puts number.to_s(16)end

(1..10).each(&puts_in_hex)

Page 5: A Gentle Introduction to Functional Paradigms in Ruby

ASIDE: THE AMPERSAND

The ampersand indicates that we're working with a blockdef use_block(&block) block.call(2)end

use_block do |number| number * 2end# => 4

Page 6: A Gentle Introduction to Functional Paradigms in Ruby

ASIDE: THE AMPERSAND

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 * 2end

use_block(&multiply_two)# => 4

Page 7: A Gentle Introduction to Functional Paradigms in Ruby

ASIDE: SYMBOL#TO_PROC

Creates a proc which will call a method on an objectcall_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_scall_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"

Page 8: A Gentle Introduction to Functional Paradigms in Ruby

ASIDE: SYMBOL#TO_PROC

In a method call, &:sym is a shortcut for :sym.to_procdef apply_block_to_array_and_print(&block) yield ['h', 'e', 'll', 'o', ' ', 'fun', 'ctions', '!']end

apply_block_to_array_and_print(&:join)# => "hello functions!"

Page 9: A Gentle Introduction to Functional Paradigms in Ruby

FILTERProblem: A list needs to be filtered

Page 10: A Gentle Introduction to Functional Paradigms in Ruby

FILTERSolution: delete everything else

# Find all of the adverbs in a word listword_list.each do |item| word_list.delete(item) unless /ly$/.match(item)end

Page 11: A Gentle Introduction to Functional Paradigms in Ruby

FILTERBetter solution: build a new list!

# Find all of the adverbs in a word listadverbs = []word_list.each do |item| adverbs << item if /ly$/.match(item)end

Page 12: A Gentle Introduction to Functional Paradigms in Ruby

FILTERBetter yet: use Enumerable#select

# Find all of the adverbs and non-adverbs in a word listadverbs = word_list.select { |item| /ly$/.match(item) }not_adverbs = word_list.reject { |item| /ly$/.match(item) }

Page 13: A Gentle Introduction to Functional Paradigms in Ruby

MAPProblem: A list needs to have elements modified

Page 14: A Gentle Introduction to Functional Paradigms in Ruby

MAPSolution: Overwrite the original list

# Square all of our numbers(1...numbers.length).each do |index| numbers[index] **= 2end

Page 15: A Gentle Introduction to Functional Paradigms in Ruby

MAPBetter Solution: generate a new list

numbers = [1,2,3,4,5]squares = []

# Square all of the numbersnumbers.each do |number| squares << number ** 2end

Page 16: A Gentle Introduction to Functional Paradigms in Ruby

MAPBetter yet: use Enumerable#map

numbers = [1, 2, 3, 4, 5]

# Square all of our numberssquares = numbers.map { |number| number ** 2 }

# Another way we could do itsquares = numbers.each_with_object(2).map(&:**)

Page 17: A Gentle Introduction to Functional Paradigms in Ruby

REDUCEProblem: A list needs to be transformed

Page 18: A Gentle Introduction to Functional Paradigms in Ruby

REDUCESolution: Iterate through the list

numbers = [1, 2, 3, 4, 5]product = 1

# Alter the product iterativelynumbers.each do |number| product *= numberend

Page 19: A Gentle Introduction to Functional Paradigms in Ruby

REDUCEBetter solution: Use Enumerable#reduce

numbers = [1, 2, 3, 4, 5]

# Calculate the product of the list membersproduct = numbers.reduce { |acc,item| acc * item }

# Shorter way to do the sameproduct = numbers.reduce(&:*)

Page 20: A Gentle Introduction to Functional Paradigms in Ruby

ZIPProblem: Two lists need to be intertwined

Page 21: A Gentle Introduction to Functional Paradigms in Ruby

ZIPSolution: Overwrite one of the lists

a = [1, 2, 3]b = [4, 5, 6]

# Intertwine list a with list ba.each_with_index do |number, index| a[index] = [number, b[index]]end

Page 22: A Gentle Introduction to Functional Paradigms in Ruby

ZIPBetter Solution: use Enumerable#zip

a = [1, 2, 3]b = [4, 5, 6]

# Intertwine list a with list bc = a.zip(b)# => [[1, 4], [2, 5], [3, 6]]

Page 23: A Gentle Introduction to Functional Paradigms in Ruby

WARNINGReligion ahead!

Page 24: A Gentle Introduction to Functional Paradigms in Ruby

STATERuby is good at state.

Mutable

Implicit

Hidden

Page 25: A Gentle Introduction to Functional Paradigms in Ruby

DANGEROUS STATE

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] # => ???

Page 26: A Gentle Introduction to Functional Paradigms in Ruby

DANGEROUS STATE

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"

Page 27: A Gentle Introduction to Functional Paradigms in Ruby

DUP TO THE RESCUE?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] # => ???

Page 28: A Gentle Introduction to Functional Paradigms in Ruby

DUP TO THE RESCUE?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"

Page 29: A Gentle Introduction to Functional Paradigms in Ruby

WELP.State is difficult to manage and trackParticularly as systems grow in complexityThings get more difficult with real threads (Rubinius, JRuby) Avoiding mutable state in most cases avoids this problem.

Page 30: A Gentle Introduction to Functional Paradigms in Ruby

BATTLING STATEAvoiding state fits well with good style:

Keep methods short and responsible for one thingWrite methods with idempotence in mindWhen mutations seem necessary, use more functions

Page 31: A Gentle Introduction to Functional Paradigms in Ruby

RULES: MADE TO BE BROKEN

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

Page 32: A Gentle Introduction to Functional Paradigms in Ruby

PAIN POINTS

Sometimes avoiding state doesn't make sense:Code runs much heavier than it couldCode runs much slower than it otherwise might (GC runs)

Page 33: A Gentle Introduction to Functional Paradigms in Ruby

PAIN MANAGEMENTWe can keep things from getting out of hand!

Keep code which has side-effects to a minimumIsolate code which produces side-effectsDon't make it easy to mutate state accidentally

Page 34: A Gentle Introduction to Functional Paradigms in Ruby

QUESTIONS? COMMENTS?