I cannot understand this piece of Smalltalk code:
[(line := self upTo: Character cr) size = 0] whileTrue.
Can anybody help explain it?
I cannot understand this piece of Smalltalk code:
[(line := self upTo: Character cr) size = 0] whileTrue.
Can anybody help explain it?
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:
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.'"
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
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
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.
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.
© 2022 - 2024 — McMap. All rights reserved.