Search code examples
loopsvariablessmalltalkparity

Why does this code set the variable a to 9?


I am confused by this piece of code:

| a b c| a := 1. b := [a := a + 1]. c := [a := a - 2. b].
        10 timesRepeat: (a even ifTrue: b ifFalse: c). a

My assumption was that this piece of code would set a to -19. Each iteration would test if a was even, but a would be odd so c would be called, substracting 2 from a without affecting its parity. c would not call b because, if my understanding of blocks is correct, the last element of the block is returned instead of evaluated; so c would return b, but timesRepeat discards whatever is returned anyway so this b in c has no effect.

My assumption was proven to be wrong: this piece of code sets a to 9 instead. To see what's going on I modified this piece of code slightly:

| a b c| a := 1. b := [Transcript show: (a displayString). a := a + 1]. c := [Transcript  show: (a displayString). a := a - 2. b.].
           10 timesRepeat: (a even ifTrue: b ifFalse: c). a

This is what gets printed:

1-1012345678

So it would seem b is called after all? Was my assumption wrong that b is returned instead of called?

Let's try to check this:

jkl := [Transcript show: 'I am called too.'].
asdf := [Transcript show: 'I am called!'. jkl].

10 timesRepeat: asdf

Nope, asdf does not call jkl here:

I am called!I am called!I am called!I am called!I am called!I am called!I am called!I am called!I am called!I am called!

And anyway, if c was always just calling b, its effect would be to effectively substract 1 from a; but this doesn't happen. Instead, the first iteration seems to call c and then, mysteriously, each iteration seems to call b instead, even if a is odd!!

What's going on here??


Solution

  • The timesRepeat: selector wants a block as an argument. You are calling it with an expression inside of parentheses:

    10 timesRepeat: (a even ifTrue: b ifFalse: c).
    

    It just so happens, though, that c is defined as the block [a := a - 2. b] which returns the value of b and that happens to be a block. So timesRepeat: is happy, and it executes the block b on each iteration in which a is odd. If you write it correctly as:

    10 timesRepeat: [a even ifTrue: b ifFalse: c].
    

    Then at the end, a will be -19 as expected.

    Regarding your statement: if my understanding of blocks is correct, the last element of the block is returned instead of evaluated, this isn't really the case. There's no special treatment for the last statement in a block other than it's result is indeed returned as the value of the block when that block is executed. Your last statement in the block is just the name of a variable. The value of the variable just happens to be a block, but no matter what it is, just having a variable name by itself as a statement in Smalltalk just returns the value of the variable. If the variable happens to be a block, you get the block. The block is not executed.

    Consider the following blocks:

    [a := 1. b := 2. c := a+b]
    

    When this block is executed, then a will have the value 1, b the value 2, and c the value 3. The value the block will return is the value of c, which is 3.

    [a := 1. b := 2. a]
    

    If you execute this block, the result will be the value of a, which is 1.

    [a := 1. b := 2. c := [a+b]. c]
    

    If you execute this block, the result will be the block that the variable c represents. It does not execute the block c. This is consistent with the prior example.

    So, when you execute the block, [Transcript show: 'I am called!'. jkl]., the jkl at the end is not executed. It's value is just returned. If you want to execute it, you could write asdf := [Transcript show: 'I am called!'. jkl value]. A block will execute when sent the value message. The result of executing block [Transcript show: 'I am called!'. jkl value]. will be the result of executing the block jkl.