3
result := String new. 
1 to: 10 do: [:n | result := result, n printString, ’ ’]. 

Everything in smalltalk is an object and the objects interact through messages.

I couldn't understand how the above code is understanding the message to:do:

How is it able to iterate the block from 1 to 10? How does it know it has to repeat the block that many number of times?

Can someone explain what happens under the hood?

2 Answers 2

8

All Smalltalk messages follow the pattern <receiver> <message>.. In this case the receiver is 1 (a subinstance of Number), and the message is to:do:.

You can browse class Number and see the implementation of to:do: right there:

to: stop do: aBlock | nextValue | nextValue := self. [nextValue <= stop] whileTrue: [aBlock value: nextValue. nextValue := nextValue + 1]

In your example, stop is 10 and aBlock is [:n | result := result, n printString, ’ ’]. So indeed, it is sending value: to aBlock repeatedly.

Now, in addition to that, many Smalltalks generate special byte code when they see the for:to: message, but this is just an optimization.

2
  • BTW, this is at least somewhat related to the Church Encoding of Numerals in λ-calculus, although not as closely as Smalltalk's encoding of Booleans, which effectively is the Church Encoding of Booleans in λ-calculus. In the Church Encoding of Numerals, numbers are encoded as iteration (so, a number and a loop with that number of iterations are the same thing), whereas in Smalltalk iteration is encoded as a method of numbers. Commented Sep 11, 2018 at 20:35
  • Also, in a Smalltalk with Proper Tail Calls, it could be implemented with tail-recursion, which may or may not be more elegant in your eyes. (It is in mine.) Commented Sep 14, 2018 at 7:14
1

Consider the method

m
  1 to: 10 do: [:i | self doSomethingWith: i]

Here are the bytecodes that Pharo generates

    pushConstant: 1                   ; i := 1
    popIntoTemp: 0                    ;
@2: pushTemp: 0                       ; i <= 10 ?
    pushConstant: 10                  ;
    send #'<='                        ;
    jumpFalse: @1                     ; if false, go to end
    self                              ; self doSomethingWith: i
    pushTemp: 0                       ;
    send #doSomethingWith:            ;
    pop                               ; 
    pushTemp: 0                       ; i := i + 1
    pushConstant: 1                   ;
    send #'+'                         ;
    popIntoTemp: 0                    ;
    jumpTo: @2                        ; loop
@1: returnSelf

As you can see the message #to:do is never sent, while #'<=' and #+ are (even though they are not in the source code!). Why? Because of what Bert said in his answer: these messages are optimized by the Smalltalk compiler. In the case of Pharo the optimization happens in #to:do:. In other dialects #to:do: is implemented in terms of #whileTrue:, which is the one that gets optimized.

Once you have understood how this works under the hood, get back to think of it as if #to:do: were a regular message with receiver 1 and arguments 10 and the block [:i | self doSomethingWith: i]. The optimization shouldn't obscure the semantics that your mind needs to follow.

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