functional programming in rubyfiles.meetup.com/1397868/rubyfpintro.pdf · ruby 1.8 gotchas >...

29
functional Programming in Ruby by Paul Barry

Upload: others

Post on 05-Jun-2020

12 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

functional Programmingin Ruby

by Paul Barry

Page 2: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

What is Functional Programming?

A programming paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data. It emphasizes the application of functions, in contrast to the imperative programming style, which emphasizes changes in state.

-- Wikipedia

Page 3: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

All programming languages are opinionated

Page 4: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Functional Programming Techniques

λ First-class Functionλ Higher-order Functionsλ Immutabilityλ Pure Functions (side-effect free)λ Recursionλ Lazy Evaluationλ Partial application / currying

Page 5: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Functional Programming Techniques in Ruby

• First-class Function

• Higher-order Functions

• Immutability

• Pure Functions (side-effect free)

• Recursion

• Lazy Evaluation

• Partial application / currying

Page 6: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

First-class functions

• Assigned to variables

• Stored in data structures

Page 7: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Higher-order functions

• Passed as arguments to other functions

• Return other functions as a result

Page 8: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Proc

> p = Proc.new {|x,y| x.to_i + y.to_i } => #<Proc:0x00000001011c5d20@(irb):14>

• Functions represented by Proc objects• Two ways to define Procs, different semantics

> l = lambda {|x,y| x.to_i + y.to_i } => #<Proc:0x00000001011b98e0@(irb):15>

Page 9: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Calling a proc> p = Proc.new {|x,y| x.to_i + y.to_i } => #<Proc:0x00000001011c5d20@(irb):14>> p[1,2] => 3 > p.call(1,2) => 3> p[] => 0 > p[1,2,3,4] => 3> p.call [1,2] => 3> p.call *[1,2] => 3

Page 10: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Calling a lambda> l = lambda {|x,y| x.to_i + y.to_i } => #<Proc:0x00000001011bc248@(irb):5>> l[1,2] => 3 > l.call(1,2) => 3> l[]ArgumentError: wrong number of arguments (0 for 2)> l[1,2,3,4]ArgumentError: wrong number of arguments (4 for 2)> l.call [1,2]ArgumentError: wrong number of arguments (1 for 2)> l.call *[1,2] => 3

Page 11: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Ruby 1.8 Gotchas> lambda{}.call(1)

> Proc.new{|x|}.call

=> nil> lambda{|x|}.call(irb):5: warning: multiple values for a block parameter (0 for 1) from (irb):5 => nil > Proc.new{}.call(1) => nil

(irb):7: warning: multiple values for a block parameter (0 for 1) from (irb):7 => nil

Page 12: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

“Fixed” in Ruby 1.9> lambda{}.call(1)ArgumentError: wrong number of arguments (1 for 0)> lambda{|x|}.call()ArgumentError: wrong number of arguments (0 for 1)> Proc.new{}.call(1) => nil > Proc.new{|x|}.call => nil

Page 13: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Ruby 1.9 ProcRocket

> ->(x,y) { x.to_i + y.to_i } => #<Proc:0x0000010097c988@(irb):1 (lambda)>> l = ->(x=1, *y, &z) { [x, y, z] } => #<Proc:0x000001009adde8@(irb):2 (lambda)>> l.call(1, 2, 3) {} => [1, [2, 3], #<Proc:0x000001008535e8@(irb):4>]

Page 14: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Kernel#proc considered harmful

• In 1.8, proc is an alias to lambda

• “Fixed” in 1.9, returns a proc

• Be careful in library code

> proc {|x,y| x.to_i + y.to_i } => #<Proc:0x000000010103e538@(irb):1>

Page 15: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Returning in a procdef foo Proc.new do puts "hi" return "whatever" end.call puts "bye"end

> foohi => "whatever"> def f(p); p.call end => nil

LocalJumpError: unexpected return> f Proc.new{ return }

Page 16: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Returning in a lambdadef foo lambda do puts "hi" return "whatever" end.call puts "bye"end

> foohibye => nil> def f(p); p.call end => nil

=> nil> f lambda{ return }

Page 17: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

proc vs. lambda

• proc does not enforce arity

• proc returns out of context

• proc is “block-like”

• lambda is “method-like”

Page 18: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Closures

• Function that maintains a reference to local variables that were in scope when the function was defined

• Both procs and lambdas are closures

Page 19: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Closure Example

def make_counter(count = 0) lambda{ count += 1 }endputs defined? count # => nilcounter = make_counterputs counter.call # => 1puts counter.call # => 2puts counter.call # => 3

Page 20: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

def is not closure

count = 0def make_counter lambda { count += 1 }endputs defined? count # => local-variablecounter = make_counterputs counter.callNoMethodError: undefined method `+' for nil:NilClass

Page 21: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Ruby’s Higher-Order Functions: Blocks

5.times do puts "hello"end

Page 22: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

All methods can have a block

"foo".reverse do puts "Never Gonna Happen"end

Page 23: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Name your block

def m(&block) p blockend

> m {}#<Proc:0x0000000000000000@(irb):1>

Page 24: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Put your proc in the block> l = lambda{|n| puts n }

01234 => 5

=> #<Proc:0x000000010103f9d8@(irb):1> > 5.times(&l)

Page 25: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

def is not closure

count = 0def make_counter lambda { count += 1 }endputs defined? count # => local-variablecounter = make_counterputs counter.callNoMethodError: undefined method `+' for nil:NilClass

Page 26: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

A method closurecount = 0Object.send(:define_method, :make_counter) do lambda { count += 1 }endputs defined? count # => local-variablecounter = make_counterputs counter.callputs counter.callputs counter.call

Page 27: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Convert a method to a proc

f = 1.method(:+).to_procp [1, 2, 3].map(&f) # => [2, 3, 4]

Page 28: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Recap

• Anonymous Functions with Proc Objects

• Proc objects are closures

• Higher-Order Functions with Block Syntax

• Methods are like closures

Page 29: functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas > lambda{}.call(1) > Proc.new{|x|}.call => nil > lambda{|x|}.call (irb):5: warning: multiple

Questions?