Skip to content

03 — Blocks, procs and lambdas

One line: A block is syntax on one method call, a proc or lambda is the object it can become, and all of them are closures — where Python has one kind of function object and spells each of these ideas differently.

This chapter is the part of Ruby a Python programmer has no word for. A block — the { |x| … } or do … end after a call — is not a value: it cannot be assigned, a call carries at most one, and yield runs it from inside the method. It becomes an object only through an &block parameter, and the object it becomes is a Proc, of which there are two kinds: a proc, which pads and drops arguments and returns from the method that wrote it, and a lambda, which checks its argument count and returns from itself. Both are closures over the variables around them — the variables, not their values — and a block parameter is a fresh variable per call, which is why the lambda-in-a-loop trap is rarer here than in Python and still present when the variable lives outside the block. The chapter then covers the short spellings, &:sym and it/_1; the three exits, next, break and return, each reaching a different distance; writing an iterator with one each and include Enumerable; and curry with the composition operators >> and <<.

The Python model carries over in two places: a nested def or lambda closes over its enclosing function's variables in the same way, and return inside a for loop leaves the function just as return inside a block leaves the method. It breaks everywhere else. Python passes callables — a lambda of one expression, an inner def, a functools.partial — where Ruby attaches a block; it has no lenient proc, no non-local return from a callback, no break with a value, no redo, no implicit it, no curry and no composition operator; and its iterator is a protocol of __iter__ and next rather than a method that yields, with filter and map lazy by default where Ruby asks for lazy. Every page measures both sides: the Ruby program and its Python twin print the same numbered rows, and where the languages differ, the row prints two different values.

Lesson Level The one thing
Blocks are not objects 101 a block rides on one call: yield, block_given?, LocalJumpError, &block reifies it, { } binds tighter than do … end
Procs and lambdas differ in arity and return 201 a lambda checks its arguments and returns from itself; a proc pads, drops, and returns from the enclosing method
Closures capture variables, not values 201 a closure sees later assignments; a block parameter is fresh per call, an outer variable is shared; a def is not a closure
&:sym calls Symbol#to_proc 201 &x calls x.to_proc; a symbol's proc calls the method on its first argument, so map(&:upcase) and inject(&:+) are one trick
it and _1 name the block's parameter 201 _1, _2 and it are parameters the parser polices: no mixing, no nested _1, none beside \|x\|; a local it shadows them
next, break and return leave a block differently 201 next v is the block's value, break v is the call's value, return leaves the method; redo repeats the iteration
An iterator is a method that yields 201 one each plus include Enumerable earns the toolbox; to_enum and Enumerator.new build lazy, even infinite, enumerators
Procs curry and compose 201 curry chains one-argument lambdas, >> and << compose any callables, parameters and arity describe the shape

Read more