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??
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
.