如果进程终止,是否可以执行整个 ensure 块?
Is it ok to execute the entire ensure block if the process is terminated?
今天我了解到在 Pharo 执行:
[v := 1] ensure: [self halt. v := 2]
即使我们在 halt
window(!) 处放弃进程,也会最终设置 v = 2,
。
我觉得这值得商榷。对我来说,#ensure:
的语义意味着序列
self halt. v := 2
必须执行,无论接收方块的情况如何,不考虑参数块的逻辑。由于 #halt
的逻辑包括终止进程的事件,我发现它干扰了第二句话的 obstinate 评估。
接下来我尝试了以下操作:
[v := 1] ensure: [1 / 0. v := 2]
当弹出 ZeroDivide
异常时,我关闭了调试器,但 v
的值仍然是 2
(与 #halt
相同)
最后,我评价:
[v := 1] ensure: [n := 1 / 0. v := v + n]
并在出现 ZeroDivide
异常时关闭调试器。这次 v
的值是 1
但我没有例外,因为 v + n
无法计算。换句话说,错误无声无息地继续下去。
所以我的问题是。这种行为背后的理性是什么?该过程不应该在 "normal" 情况下终止的点终止,即不涉及 #ensure:
吗?
有趣的一个。看来你的答案在于方法 BlockClosure>>valueNoContextSwitch
,它被 #ensure:
调用。如果你在那里阅读评论,它说它创建了 BlockClosure>>value
的精确副本(在原始形式中),并且该副本的 return 值得到 returned,而不是 return 包含您终止的 halt
的原始块的值。所以副本被执行(显然忽略了复制的halt
),即使原始文件没有完成。
我的猜测是,这是为了确保(没有双关语意)ensure:
块始终运行,但具有忽略原始块终止的意外副作用。我同意你的看法,这不仅违反直觉,而且可能不是预期的。
我想这是任何 (ANSI) 标准都没有完全定义的行为,但如果我错了,请纠正我。
其他 Smalltalks 似乎表现不同。我在 Smalltalk/X 中尝试了它,其中调试器提供了 3 个选项:"Continue"(即继续)、"Abort"(即展开)和 "Terminate"(即终止进程)。我想 "Terminate" 对应于关闭调试器时 Squeak 所做的事情。
使用 "Abort" 和 "Terminate",确保块的其余部分不执行,使用 "Continue" 则执行。我想这没关系,你会期望什么。
在中止和终止(它们都展开到相应的异常处理程序)时,它不应尝试重新评估或继续潜在的 wrong/bad/failing 确保块。
是否要继续由处理程序(基本上是调试器)选择。如果不是,那么它应该离开 ensure 块并继续执行调用链中可能位于上方的任何其他 ensure 块。
这与异常处理块的行为一致,如果在其中引发相同的异常,也不会重新评估或继续处理异常处理块的行为。在 ST/X 中,异常 类 中有明确的代码来处理这种情况,所以它肯定是有目的的,而不是副作用。
我猜这在 Squeak 中是错误的,应该告知 Squeak 开发人员。
今天我了解到在 Pharo 执行:
[v := 1] ensure: [self halt. v := 2]
即使我们在 halt
window(!) 处放弃进程,也会最终设置 v = 2,
。
我觉得这值得商榷。对我来说,#ensure:
的语义意味着序列
self halt. v := 2
必须执行,无论接收方块的情况如何,不考虑参数块的逻辑。由于 #halt
的逻辑包括终止进程的事件,我发现它干扰了第二句话的 obstinate 评估。
接下来我尝试了以下操作:
[v := 1] ensure: [1 / 0. v := 2]
当弹出 ZeroDivide
异常时,我关闭了调试器,但 v
的值仍然是 2
(与 #halt
相同)
最后,我评价:
[v := 1] ensure: [n := 1 / 0. v := v + n]
并在出现 ZeroDivide
异常时关闭调试器。这次 v
的值是 1
但我没有例外,因为 v + n
无法计算。换句话说,错误无声无息地继续下去。
所以我的问题是。这种行为背后的理性是什么?该过程不应该在 "normal" 情况下终止的点终止,即不涉及 #ensure:
吗?
有趣的一个。看来你的答案在于方法 BlockClosure>>valueNoContextSwitch
,它被 #ensure:
调用。如果你在那里阅读评论,它说它创建了 BlockClosure>>value
的精确副本(在原始形式中),并且该副本的 return 值得到 returned,而不是 return 包含您终止的 halt
的原始块的值。所以副本被执行(显然忽略了复制的halt
),即使原始文件没有完成。
我的猜测是,这是为了确保(没有双关语意)ensure:
块始终运行,但具有忽略原始块终止的意外副作用。我同意你的看法,这不仅违反直觉,而且可能不是预期的。
我想这是任何 (ANSI) 标准都没有完全定义的行为,但如果我错了,请纠正我。
其他 Smalltalks 似乎表现不同。我在 Smalltalk/X 中尝试了它,其中调试器提供了 3 个选项:"Continue"(即继续)、"Abort"(即展开)和 "Terminate"(即终止进程)。我想 "Terminate" 对应于关闭调试器时 Squeak 所做的事情。
使用 "Abort" 和 "Terminate",确保块的其余部分不执行,使用 "Continue" 则执行。我想这没关系,你会期望什么。
在中止和终止(它们都展开到相应的异常处理程序)时,它不应尝试重新评估或继续潜在的 wrong/bad/failing 确保块。
是否要继续由处理程序(基本上是调试器)选择。如果不是,那么它应该离开 ensure 块并继续执行调用链中可能位于上方的任何其他 ensure 块。
这与异常处理块的行为一致,如果在其中引发相同的异常,也不会重新评估或继续处理异常处理块的行为。在 ST/X 中,异常 类 中有明确的代码来处理这种情况,所以它肯定是有目的的,而不是副作用。
我猜这在 Squeak 中是错误的,应该告知 Squeak 开发人员。