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

Post on 05-Jun-2020

12 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

functional Programmingin Ruby

by Paul Barry

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

All programming languages are opinionated

Functional Programming Techniques

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

Functional Programming Techniques in Ruby

• First-class Function

• Higher-order Functions

• Immutability

• Pure Functions (side-effect free)

• Recursion

• Lazy Evaluation

• Partial application / currying

First-class functions

• Assigned to variables

• Stored in data structures

Higher-order functions

• Passed as arguments to other functions

• Return other functions as a result

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>

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

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

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

“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

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>]

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>

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 }

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 }

proc vs. lambda

• proc does not enforce arity

• proc returns out of context

• proc is “block-like”

• lambda is “method-like”

Closures

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

• Both procs and lambdas are closures

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

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

Ruby’s Higher-Order Functions: Blocks

5.times do puts "hello"end

All methods can have a block

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

Name your block

def m(&block) p blockend

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

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

01234 => 5

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

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

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

Convert a method to a proc

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

Recap

• Anonymous Functions with Proc Objects

• Proc objects are closures

• Higher-Order Functions with Block Syntax

• Methods are like closures

Questions?

top related