Blocks, Procs & Lambdas!

Doug Price / @solipet

a beautifully drawn frog, very realistic actually

September 12, 2017

Blocks

A block is one of those blobs of code we see all the time:
``` ruby 1..10.each do |n| puts "The #{n.ordinalize} time around..." end ```

or

``` ruby 1..10.each {|n| puts "The #{n.ordinalize} time around"} ```

How do blocks work?

Methods that take blocks can call yield to execute them.
``` ruby def take_a_block puts "Before the yield..." yield puts "... after the yield" end ```

What if there's no block?

``` ruby take_a_block # => throws a LocalJumpError: no block given (yield) ```
To fix this, check first!
``` ruby def take_a_block puts "Before the yield..." yield if block_given? puts "... after the yield" end ```

Block parameters

Blocks can take arguments:
``` ruby def countdown (1..99).reverse_each do |n| yield n if block_given? end end countdown {|n| puts "#{n} bottles of beer..."} 99 bottles of beer... 98 bottles of beer... ... ```

Reference a block

Blocks can be declared as arguments and re-used:
Can’t yield in this case, use call instead.
``` ruby def countdown(&block) (1..99).reverse_each do |n| block.call n if block_given? if n < 10 puts " Oy, I'm starting to repeat myself!" block.call n if block_given? end end end countdown {|n| puts "#{n} bottles of beer..."} ... 1 bottles of beer... Oy, I am starting to repeat myself! 1 bottles of beer... ```

Procs

"Proc objects are blocks of code that have been bound to a set of local variables. Once bound, the code may be called in different contexts and still access those variables."
Source: https://ruby-doc.org/core-2.4.1/Proc.html

Are Blocks Procs?

``` ruby def take_a_block(&block) puts "My block is a #{block.class}!" end take_a_block { "foo" } My block is a Proc! ```

Save a proc to a variable

Procs can be saved to a var and passed as an argument to a method in lieu of a block.
``` ruby my_proc = Proc.new {|n| puts "#{n} bottles of beer..."} ```
Proc.new takes a block... wha?
``` ruby my_proc.call("Lots of ") Lots of bottles of beer... countdown(&my_proc) 99 bottles of beer... 98 bottles of beer... ... ```

Blocks vs. Procs

Blocks are not procs, procs are not blocks.

Procs are objects that take a block on creation and execute that block on Proc#call.

Blocks are syntax that allows us to pass a blob of code into a method expecting it.

But our block was a Proc...

``` ruby def take_a_block(&foo) puts "My block is a #{foo.class}!" end take_a_block { "foo" } My block is a Proc! ```
The &foo syntax tells the method to take the block given and convert it implicitly into a Proc object!

Procs to Blocks?

``` ruby def countdown_yield (1..99).reverse_each do |n| yield n if block_given? end end countdown_yield(&my_proc) 99 bottles of beer... 98 bottles of beer... ... ```
The &my_proc syntax "does two things: it triggers a call to my_proc's to_proc method, and it tells Ruby that the resulting Proc object is serving as a code block stand-in." - The Well-Grounded Rubyist

Fun with procs

Let's repeat that: & triggers to_proc
``` ruby Instead of ('a'..'z').map {|char| char.capitalize} => ["A", "B", "C", "D", "E", "F", "G", "H", "I", ...] ```
``` ruby Try this ('a'..'z').map &:capitalize => ["A", "B", "C", "D", "E", "F", "G", "H", "I", ...] ```
The & triggers Symbol#to_proc which returns a Proc object that responds with the method named by the symbol.

Symbol#to_proc

How does Symbol#to_proc work?
``` ruby class Symbol def to_proc Proc.new do |arg0, *args| arg0.send(self, *args) end end end ```
Thanks, Nick!

Lambdas

Lambdas are ... Procs!
but special!

Lambda creation

With a code block:
``` ruby my_lamb = lambda {|n| puts "#{n} bottles of beer..."} ```
stabby lambda syntax:
``` ruby my_lamb = -> (n) {puts "#{n} bottles of beer..."} ```
-> ~= λ

Lambdas are Special Procs

``` my_proc = Proc.new {|n| "${n} bottles of beer..."} => #< Proc:0x007fd7f1437260@(irb):126> my_proc.lambda? => false my_lamb = -> (n) {"#{n} bottles of beer..."} => #< Proc:0x007fd7f1425970@(irb):127 (lambda)> my_lamb.lambda? => true ```

Lambda parameters

Must provide the correct number of parameters (like a method).
``` ruby my_lamb.call ArgumentError: wrong number of arguments (0 for 1) my_proc.call # => nil ```

Lambda return

A lambda can return from its code block, while a Proc will return from the calling method, or return an error from the top-level context:
``` ruby return_proc = Proc.new { puts "I'm out of here!" ; return } return_lamb = -> { puts "I'm out of here!" ; return } def take_a_proc(&arg) puts "Before the call..." arg.call puts "... after the call." end take_a_proc &return_lamb Before the call... I'm out of here! ... after the call. take_a_proc &return_proc Before the yield... LocalJumpError: unexpected return def call_a_proc puts "Before the call..." arg = Proc.new { puts "I'm out of here!" ; return } arg.call puts "... after the call." end call_a_proc Before the call... I'm out of here! => nil ```

Functional Programming

Sadly, I'm not going to go into this tonight, but there are lots of examples on the interwebs.

Closures

Both procs and lambdas are also closures, i.e., any variables that are in scope when the proc is created, will remain in scope when the proc is called.

An example of Closures

``` ruby def call_a_proc(proc_arg) x = "defined in the method" puts x proc_arg.call puts x end x = "defined outside the method" p = Proc.new { puts x } call_a_proc p defined in the method defined outside the method defined in the method => nil ```

Questions?

Slides online at http://solipet.github.io/procs_and_lambdas/