Blocks (aka lambda expressions or closures) are versatile and powerful. I like them. I have even implemented a programming language in which they play a central role.
However, for a number of common programming situations, I think that some alternative approaches based on higher order messaging provide better solutions than classic block-based approaches. Marcel Weiher provides a good example in Blocked-C. He shows some Objective-C code that uses a block to iterate over a collection of strings and to create a new collection with some suffix added to the original strings:
NSMutableArray *filteredItems = [NSMutableArray array];
[items enumerateObjects WithOptions:0 withBlock:
^(id item, NSUInteger index, BOOL *stop) {
[filteredItems addObject:[item stringByAppendingString:@"suffix"]];
}
];
Then, he goes on to show how the same can be achieved using a higher order messaging (HOM) technique:
[[items collect] stringByAppendingString:@"suffix"];
The conciseness and expressiveness of the HOM version is impressive and should at the very least warrant further examination. Regarding this, we are lucky since this approach, and its application to Objective-C, is described in an OOPSLA paper titled "Higher Order Messaging" (download).
With some support in the programming language, higher order messaging techniques can go even further. F-Script is a Smalltalk dialect for Cocoa which extends Smalltalk with array programming and messaging patterns. Messaging pattern is a generalization of message sending that applies to entire collections of objects. In a number of common situations, this provides an interesting alternative to the classic block-based approach. Indeed, here is our example coded in F-Script:
items stringByAppendingString:'suffix'.
A key aspect of this programming model (and of Weiher’s HOM) is that it liberates us from the classic "one object at a time" style, and lets us manipulate whole collections of objects at once, using higher level constructs. We are freed from the low level details of accessing and dealing with individual elements. In my experience, introducing array programming in object-oriented programming, as F-Script does, has positive effects similar to those of the introduction of vectors in math, which is quite nice… This model is described in another OOPSLA paper titled "OOPAL: Integrating Array Programming in Object-Oriented Programming" (download)
To get a better sense of this programming model, let’s see how it stacks up against classic block based approaches for collections manipulation as seen in Ruby and Smalltalk. For the same price, I also add Python’s list comprehensions and C# Linq to the mix. In some common situations, those are interesting alternatives to blocks that are still representative of the "one object at a time" approach.
In the example below, we have a list of songs (a collection of Song objects) and we want to get their names. Here is how we’d do it using various approaches:
- Smalltalk and Ruby with blocks
- Python with list comprehension
- C# with Linq
- F-Script with array programming
Smalltalk |
songs collect:[:aSong| aSong name] |
Ruby |
songs.collect(&:name) |
Python |
[aSong.name() for aSong in songs] |
C# |
from aSong in songs select aSong.name() |
F-Script |
songs name |
We can note that the array programming version (F-Script) is free from syntactic noise and low-level iteration logic, as is the brain of the programmer. Actually, in that particular situation, it seems hard to do better.
For the ruby version, we use a feature adopted in Ruby 1.9, which allows for automatic conversion of symbols to blocks. This is a useful trick as it provides a way, in some situations, to have a compact notation for some kind of blocks. Note, however, that when we need to provide more that just a method name, we have to use the classic Ruby block notation. This is the case in our original example (i.e., invoking the stringByAppendingString
method with a given argument), for which we would have to do: items.collect {|item| item.stringByAppendingString("suffix")}
.
We can carry on the comparison further by introducing some need for filtering. In the following example, we want to get the names of songs with a bit rate greater than or equal to 256 kb/s.
Smalltalk |
(songs select:[:aSong| aSong bitRate >= 256]) collect:[:aSong| aSong name] |
Ruby |
songs.select {|aSong| aSong.bitRate >= 256}.collect(&:name) |
Python |
[aSong.name() for aSong in songs if aSong.bitRate() >= 256] |
C# |
from aSong in songs select aSong.name() where aSong.bitRate() >= 256 |
F-Script |
songs name where: songs bitRate >= 256 |
We see that the F-Script programming model allows for conciseness and expressiveness. Note that, in F-Script, where:
is not a keyword or a special construct. It is just a regular message selector. The where:
method, defined in a category of NSArray, takes an array of booleans as argument and returns the elements of the receiver whose positional matching element in this boolean array is true. Such boolean array is exactly what is produced by the expression songs bitRate >= 256
, as messages are automatically broadcasted to each individual elements of the receivers (i.e., the bitRate
message is sent to each element of the songs
array, producing an array of numbers whose elements are then sent the >= 256
message, producing the array of booleans). This is the powerful compression operation of array programming adapted to object-oriented programming.
Finally, here is an example of sorting a collection of objects. In this example, we want to create a sorted collection of our songs, sorting on the songs’ names. In the case of Python, since its list comprehensions don’t have specific support for sorting, we use a solution based on lambda expressions.
Smalltalk |
songs asSortedCollection:[:song1 :song2| song1 name < song2 name] |
Ruby |
songs.sort_by(&:name) |
Python |
sorted(songs, key=lambda aSong: aSong name) |
C# |
from aSong in songs orderby aSong.name() select aSong |
F-Script |
songs at: songs name sort |
As in the previous examples, the F-Script version shows that we can do sophisticated collection manipulation without relying on blocks but only using message passing. The result of the sort
message sent to the array of song names is an array of integers containing the indices that will arrange the receiver of sort
in ascending order. We then pass this array of indices to the at:
method. The at:
method allows for indexing an array by either a single integer or a whole array of indices. This is this latter capability, a fundamental element of array programming, that we use here to produce our sorted list of songs.
Conclusion
Blocks are great, especially in programming languages which don’t have (and don’t want) specific syntax for classic control structures (if/then/else, while, etc.). However, we should take them with a grain of salt and see that, for some common problems, there are alternative approaches that are worth considering.
Further reading
F-Script documentation set
Blocked-C II
Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs
Special bonus
You can easily, and interactively, experiment with the code provided in the examples: launch F-Script.app, connect to iTunes and immediately start querying and manipulating your iTunes song collection from F-Script.
You connect to iTunes using the standard Cocoa Scripting Bridge technology. For example, you can enter the following code in the F-Script console:
iTunes := SBApplication applicationWithBundleIdentifier:'com.apple.iTunes'
The iTunes
variable now points to a dynamically generated Cocoa object that stands for the iTunes application.
You can get at your songs with:
songs := ((iTunes sources at:0) playlists at:1) tracks
You can then directly paste, in the console, the code of the examples provided in this article. If your song library is huge you might notice some latency, as Cocoa messages are automatically converted to Apple Events that flow back and forth between F-Script and iTunes.
Read Full Post »