Affordances: Blocks
This post is part of an ongoing series about Affordances and Programming Languages.
Blocks (a.k.a. lambdas, closures, or block closures) are very interesting constructs that have a profound impact on the kinds of solutions we build in a programming language. Different languages support them (or not) in different ways and at different levels.
I’m most familiar with blocks in Ruby and Smalltalk, so I’ll focus on those two languages here, but I will note that C++ just added official support for lambdas in C++11, and Lisp has had lambdas for as long as I’ve been familiar with the language (20+ years).
When I started learning Ruby after having done Smalltalk for many years, I found Ruby’s blocks very familiar and comforting. However, there are some differences in how blocks are implemented that result in different affordances, and thus different idioms.
In Visualworks Smalltalk, blocks are instances of BlockClosure, and so are first-class objects with their own API. There is a literal syntax for creating blocks using square brackets.
Smalltalk blocks can be used just like any other object: You can create them, pass them as arguments to messages, store them in variables, and send messages to them. For example, you can easily time or profile some code by wrapping it in a block and sending a message to it:
Like Ruby, Smalltalk uses blocks for enumerating collections. One of
the key differences from Ruby blocks is that you can easily pass as
many blocks as you like to a method. Because of this, Smalltalk’s
version of #detect:ifNone:
is cleaner than Ruby’s:
I suspect that a lot of Ruby programmers don’t even know that
:detect
(a.k.a. :find
) takes the optional ifNone
parameter.
Another advantage to being able to pass multiple blocks to a method is that blocks can be the key element in flow control. Smalltalk has no built in operators for loops and conditionals; everything is done using objects, messages, and blocks.
Blocks are also the foundation of Smalltalk’s exception handling:
Since blocks are first-class objects in Smalltalk, you can do some
very clever things. For example, the Assets package in Visualworks
Smalltalk implements the idea of a CachedBlockClosure
, or #once
block. If you send #once
to a block, the value of the block is
computed and the block is changed into an instance of
CachedBlockClosure
containing the cached value. Sending #once
to
the CachedBlockClosure
simply returns the cached value, rather than
recomputing it. In Assets, this is used to construct and cache icons
and such using only Smalltalk code.
In Ruby, a block can be passed to any method simply by appending the
block to the message send, either using curly braces or do ... end
.
There is no need to explicitly list the block parameter in the
argument list unless you need to pass it on to another method.
Normally, the block is just an extra, implicit parameter to the method.
The method may or may not choose to make use of the block.
Blocks in Ruby are not first-class objects with their own API, but
they can easily be converted to Proc
s which are. In order to pass
multiple blocks into a method, all but one of them need to be turned
into a Proc
or lambda
first, as in the :detect
example above.
Ruby’s blocks are used a lot for configuration and customization, as well as for providing resource acquisition/release wrappers around code. For example:
There’s no reason Smalltalk’s blocks couldn’t be used this way, but there tends to be less of this pattern in Smalltalk code for some reason.
Ruby provides the block_given?
method that allows a method to
determine if a block was passed or not. Smalltalk has no such
facility; if a method takes a block, then that block must be passed,
even if it doesn’t do anything.
I’ve outlined some of the differences between Ruby and Smalltalk blocks here. There are others, but overall there is a different “feel” to how blocks are used in the two languages. There is enough similarity to make them feel familiar, but the two languages have developed distinct usage patterns around blocks.
The idiomatic use of blocks in each of the languages seems very consistent with the rest of the language, but I think that is because blocks are a such a large contributor to the feel of each language. Both Smalltalk and Ruby are largely designed around their block implementations, and so each has developed its own personality because of them.