(CMP407) Lambda as Cron: Scheduling Invocations in AWS Lambda
functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas >...
Transcript of functional Programming in Rubyfiles.meetup.com/1397868/RubyFPIntro.pdf · Ruby 1.8 Gotchas >...
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?