Discussion on:

Message 3 of 7
0 Votes
+ -
There are no statements
I see what you mean. The form of the language is kind of confusing from my vantage point, but it does lend itself to conciseness. It looks similar to languages that have statements, since it has imperative keywords, like "class," "def," "if," "for-in," etc., yet it acts like a language that doesn't. It was confusing for me to see do-end used synonymously with { }. They are the same. I found an article on style usage for these two. It suggests using braces for the case where you're using the resulting value of a call with a block for a subsequent call, and to use do-end when you're just executing a sequence of actions. Sounds good to me.

This is just personal taste, but IMO in Smalltalk the structure of things is (usually) clearer. Since Ruby borrowed some features from it, it might be useful to note the similarities and differences. You can probably see why I was confused at first from this. In standard Smalltalk, blocks are denoted by [ ] (and this notation is "owned" exclusively by the BlockClosure class). Your first example would be written as such in Smalltalk:
foo := #(1 2 3 4 5 6 7 8 9 10). "1-based array"
vals := #(0 1) asOrderedCollection.
foo do: [:n |
Transcript
show: n asString, ': ',
(vals at: n
ifAbsentPut: [(vals at: n - 1) +
(vals at: n - 2)]) asString;
cr]

"alternately, we could get rid of 'foo' and just say:
1 to: 10 do: [ ... ]

In the Ruby case, this would be equivalent to doing:
0..9.each { ... }"

The "do:" message in the above Smalltalk code is sent to the array, containing the block as a parameter value. It does the same thing as "each" in Ruby. So in Smalltalk "do" is a message, but in Ruby, do-end is a construct that is translated into a block.

The reason I thought of adding the parentheses in my first comment is I'm used to blocks being objects in and of themselves that can receive their own messages, particularly in an OOP language. I realize you didn't put parentheses around the "reject" call, with the do-end construct (that would've looked pretty ugly), but as I was writing out my "translation" of what you did, it just didn't seem right to put ".each" right after { }. I thought it might look nearly as confusing as your example. Seeing this would normally cause me to assume that I'm sending "each" to the block, not to the array that came as a result of "reject". After all, the dot syntax suggests sending a message to the preceding object, as in "self.reject".

A way one can deal with situations in Smalltalk where you want to sequence or combine actions in a single statement, particularly if it's a common pattern, is to create a method for it. For example, to do what you did in the last example, one can do this with a collection:
self reject: [:element | element \\ 2 ~= 0]
thenDo: [:element | aBlock value: element]
The "value" message is equivalent of doing a "yield" on a block, at least the way I see it used here. These same actions could be done without a method, but it would involve doing what I did in my first comment, wrapping the "reject" call in parentheses, and then sending a "do:" message on the temporary object, with the 2nd block.

One might wonder, "Why create a method," but it's like your File.open().each_line example. It makes code more concise, readable, and reliable. In Smalltalk it extends into control structures. For example, if, while, and range loops (which are like for loops, or for-each loops) are all implemented in a similar way to this--using message passing. The implication of this is that by using blocks you can create your own control structures. Very cool! Anyway, I put this out for inspiration.
Posted by Mark Miller
Updated - 3rd Jul