Today I've learned that in Pharo the execution of:
[v := 1] ensure: [self halt. v := 2]
will end up setting v = 2,
even when we abandon the process at the halt
window(!).
I find this debatable. For me, the semantics of #ensure:
means that the sequence
self halt. v := 2
must be executed, regardless the circumstances with the receiver block, not regardless the logic of the argument block. And since the logic of #halt
includes the event of terminating the process, I find it intrusive the obstinate evaluation of the second sentence.
Next I tried the following:
[v := 1] ensure: [1 / 0. v := 2]
When the ZeroDivide
exception popped-up I closed the debugger and still the value of v
was 2
(same as with #halt
.)
Finally, I evaluated:
[v := 1] ensure: [n := 1 / 0. v := v + n]
and closed the debugger on the ZeroDivide
exception. This time the value of v
was 1
but I got no exception from the fact that v + n
cannot be evaluated. In other words, the error went on silently.
So my question is. What's the rational behind this behavior? Shouldn't the process just terminate at the point it would terminate under "normal" circumstances, i.e., with no #ensure:
involved?
I guess this is behavior which is not fully defined by any (ANSI) standard, but correct me, if I am wrong.
Other Smalltalks seem to behave different. I tried it in Smalltalk/X, where the Debugger offers 3 options: "Continue" (i.e. proceed), "Abort" (i.e. unwind) and "Terminate" (i.e. kill the process). I guess "Terminate" corresponds to what Squeak does when you close the debugger.
With "Abort" and "Terminate", the rest of the ensure block is NOT executed, with "Continue" it is. I guess that is ok, and what you would expect.
On Abort and Terminate (which are both unwinds to corrsponding exception handlers), it should not try to reevaluate or proceed the potentially wrong/bad/failing ensure block.
It is be the choice of the handler (which the Debugger basically is) if it wants to proceed or not. If not, then it should get out of the ensure block and continue to execute any other ensure blocks which may be above in the calling chain.
This is consistent with the behavior of exception handling blocks, which are also not reevaluated or proceeded if the same exception is raised within. In ST/X, there is explicit code in the exception classes which cares for this situation, so it is definitely by purpose and not by side effect.
My guess is that this is wrong in Squeak and the Squeak developers should be told.
Interesting one. It seems that your answer lies in the method BlockClosure>>valueNoContextSwitch
, which is called by #ensure:
. If you read the comment there, it says that it creates an exact copy of BlockClosure>>value
(in a primitive), and the return value of that copy gets returned, not the return value of the original block containing your halt
which you terminated. So the copy gets executed (apparently ignoring the copied halt
), even if the original doesn't get to finish.
My guess is that this is intended to ensure (no pun intended) that the ensure:
block always runs, but has the unintended side effect of ignoring the termination of the original block. I agree with you that this is not only counter-intuitive, but also probably not what was intended.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With