5

I cannot understand this piece of Smalltalk code:

[(line := self upTo: Character cr) size = 0] whileTrue.

Can anybody help explain it?

5 Answers 5

12

One easy thing to do, if you have the image where the code came from, is run a debugger on it and step through.

If you came across the code out of context, like a mailing list post, then you could browse implementers of one of the messages and see what it does. For example, #size and #whileTrue are pretty standard, so we'll skip those for now, but #upTo: sounds interesting. It reminds me of the stream methods, and bringing up implementors on it confirms that (in Pharo 1.1.1), ReadStream defines it. There is no method comment, but OmniBrowser shows a little arrow next to the method name indicating that it is defined in a superclass. If we check the immediate superclass, PositionableStream, there is a good method comment explaining what the method does, which is draw from the stream until reaching the object specified by the argument.

Now, if we parse the code logically, it seems that it:

  • reads a line from the stream (i.e. up to a cr)
    • if it is empty (size = 0), the loop continues
    • if it is not, it is returned

So, the code skips all empty lines and returns the first non-empty one. To confirm, we could pass it a stream on a multi-line string and run it like so:

line := nil.
paragraph := '


this is a line of text.
this is another line
line number three' readStream.
[(line := paragraph upTo: Character cr) size = 0] whileTrue.
line. "Returns 'this is a line of text.'"
3
  • Thanks friend, your answer helps me a lot:) by the way, I thinks workmanlike fixing code in debugger is a powerful skill. case some mothod is not very easy to debug , except for method inheritage from String Category, rigth?
    – parsifal
    Commented Jan 30, 2011 at 6:20
  • You're welcome! I'm not sure what your other question is, can you say it a different way? Commented Jan 30, 2011 at 7:40
  • Which I mean is how to effectively deal with bugs in debugger. I think it's a big questions :)
    – parsifal
    Commented Feb 10, 2011 at 1:55
1

Is this more readable:

while(!strlen(line=gets(self)))

Above expression has a flaw if feof or any other error, line==NULL
So has the Smalltalk expression, if end of stream is encountered, upTo: will answer an empty collection, and you'll have an infinite loop, unless you have a special stream that raises an Error on end of stream... Try

String new readStream upTo: Character cr
1

The precedence rules of Smalltalk are
first: unary messages
second: binary messages
third: keyword messages
last: left to right

This order of left to right, can be changed by using parenthesis i.e. ( ) brackets. The expression within the pair of brackets is evaluated first. Where brackets are nested, the inner-most bracket is is evaluated first, then work outwards in towards the outer bracket, and finally the remains of the expression outside the brackets.

Because of the strong left-to-right tendency, I often find it useful to read the expression from right to left.

So for [(line := self upTo: Character cr) size = 0] whileTrue.

Approaching it from the end back to beginning gives us the following interpretation.

. End the expression. Equivalent to ; in C or Java

whileTrue What's immediately to the left of it? ] the closure of a block object.

So whileTrue is a unary message being sent to the block [ ... ] i.e. keep doing this block, while the block evaluates to true

A block returns the result of the last expression evaluated in the block.

The last expression in the block is size = 0 a comparison. And a binary message.

size is generally a unary message sent to a receiver. So we're checking the size of something, to see if it is 0. If the something has a size of 0, keep going.

What is it we are checking the size of? The expression immediately to the left of the message name. To the left of size is
(line := self upTo: Character cr)

That's what we want to know the size of.

So, time to put this expression under the knife.

(line := self upTo: Character cr) is an assignment. line is going have the result of
self upTo: Character cr assigned to it.

What's at the right-hand end of that expression? cr It's a unary message, so has highest precedence. What does it get sent to. i.e. what is the receiver for the cr message?

Immediately to its left is Character. So send the Character class the message cr This evaluates to an instance of class Character with the value 13 - i.e. a carriage return character.

So now we're down to self upTo: aCarriageReturn

If self - the object receiving the self upTo: aCarriageReturn message - does not understand the message name sizeUpto: it will raise an exception.

So if this is code from a working system, we can infer that self has to be an object that understands sizeUpTo: At this point, I am often tempted to search for the massage name to see which Classes have the message named sizeUpto: in their list of message names they know and understand (i.e. their message protocol ).

(In this case, it did me no good - it's not a method in any of the classes in my Smalltalk system).

But self appears to be being asked to deal with a character string that contains (potentially) many many carriage returns.

So, return the first part of aCharacterString, as far as the first carriage-return.

If the length of aCharacterString from the start to the first carriage return is zero, keep going and do it all again.

So it seems to be we're dealing with a concatenation of multiple cr-terminated strings, and processing each one in turn until we find one that's not empty (apart from its carriage- return), and assigning it to line

0

I find that a different style of formatting helps. So instead of

[(line := self upTo: Character cr) size = 0] whileTrue.

I would reformat this to

[ (line := self upTo: Character cr) size = 0
] whileTrue.
-2

One thing about Smalltalk that I'm personally not a huge fan of is that, while message passing is used consistently to do nearly everything, it can sometimes be difficult to determine what message is being sent to what receiver. This is because Smalltalk doesn't have any delimiters around message sends (such as Objective-C for example) and instead allows you to chain message sends while following a set of precedence rules which go something like "message sends are interpreted from left to right, and unless delimited by parentheses, messages with many keywords are evaluated first, then binary keyword messages, then unary, and then no keyword ones." Of course using temporary variables or even just parentheses to make the order of the messages explicit can reduce the number of situations where you have to think about this order of operations. Here is an example of the above code, split up into multiple lines, using temp variables and parenthesis for explicit message ordering for readability. I think this is a bit clearer about the intent of the code:

line = (self upTo: (Character cr)).

([((line size) = 0)] whileTrue).

So basically, line is the string created when you concatenate the characters in string self up until the carriage return character (Character cr). Then, we check line's size in characters, and check if that's equal to 0, and because we put this in a block (brackets), we can send it a whileTrue, which re-evaluates the condition in the block until it returns true. So, yeah whileTrue really would be clearer if it was called doWhileTrue or something like that.

Hope that helps.

2
  • 1
    "[...] precedence rules which go something like "message sends are interpreted from left to right, and unless delimited by parentheses, messages with many keywords are evaluated first, then binary keyword messages, then unary, and then no keyword ones." - that's wrong: rules are: unary messages have higher precedence than binary, which have higher precedence than keyword msgs. Parenthesis may be used to override that default. Of course, you are right that complicated expressions may be hard to read, and occasional parentheses are useful to emphasize on the order - even if not needed strictly.
    – blabla999
    Commented Mar 26, 2012 at 11:28
  • After a few years of smalltalk, and some more of Java, Delphi, Javascript and a dozen others, I have to say that I find the simple precedence rules of smalltalk far superior and much less likely to lead to bugs. The chaining (cascading) helps a lot in creating DSLs and fluent interfaces Commented Dec 18, 2014 at 9:59

Not the answer you're looking for? Browse other questions tagged or ask your own question.