根据Coursera "Programming Languages, Part A"课程的“第3节摘要”(华盛顿大学的Dan Grossman):
但您已经看到一个功能更像是动态范围而不是词法范围:异常处理。当引发异常时,评估必须“查找”应该评估哪个句柄表达式。这种“查找”是使用动态调用堆栈完成的,不考虑程序的词法结构。
我认为作者正在谈论标准ML,但C ++似乎也在做同样的事情。是否所有语言都使用此类动态查找进行“异常处理”?
答案 0 :(得分:3)
是的,这就是异常处理的工作方式。
FWIW,我们今天所理解的异常处理是在70年代以CLU语言发明的,并在80年代早期在ML中进一步发展。从那些传播到其他语言(如C ++)的文本中,大多数只是关于如何构造和匹配异常的变体。
值得注意的是,异常处理只是最近发明的一种名为effect handlers的通用机制的特例,它更丰富并且可以表达各种其他控制结构,如协同程序,生成器,异步/等待,甚至回溯等等。它对异常处理的主要补充是处理程序可以恢复抛出计算,传回一个值。与异常处理一样,它的所有应用程序都非常依赖于处理程序的动态范围。
答案 1 :(得分:0)
(修订回答)
所有语言都做"异常处理"有这样的动态查找?
如果定义异常处理总是意味着:展开调用堆栈并寻找异常处理程序,那么相似性是不可避免的。但是" 喜欢动态范围和#34;之间存在差异。和" 动态范围"。
顺便提一下,标准ML允许本地定义的异常,并且不提供introspection值,包括异常。例如,以下程序未键入check:
fun foo () =
let exception Foo
in bar () handle Foo => true
| _ => false
end
and bar () = raise Foo
它确实强制执行与动态范围的标识符查找机制类似的控制流,但处理程序不会继承命名异常的范围以与其父处理程序匹配。因此,我认为将标准ML异常甚至更像动态范围是不合理的。 dynamic programming languages中的常见功能,例如从基类继承的异常和运行时类型注释,使得类比更强。
还有其他错误处理系统将控制流传递到调用堆栈中的其他位置而不是向上传递。例如,您可以将Erlang的process linking视为一种异常处理机制,其中多个链接的进程可以处理进程退出的事件。这里,控制流只是模糊地类似于动态范围,因为不存在严格的层次结构,而是异常处理程序的图形。它也不是非常接近任何常见的异常定义,即使进程崩溃是例外。
答案 2 :(得分:0)
考虑一段这样的代码:
fun add (a, b) = a + b
fun double a = add (a, b)
val _ = double 10
当add
返回时,它将控制返回到调用堆栈上的下一个函数调用,即double
。我认为将其视为“动态范围”并不重要;相反,这只是调用堆栈由于是一个调用堆栈而提供的基本功能。
同样,当add
引发异常(例如Overflow
)时,也会将控制权返回给double
。 (当然,double
没有处理任何异常,因此它会隐式重新引发其自己的调用者的所有异常。)Grossman博士显然已经考虑到标准ML实现将保持动态最内层处理程序的-locally-scoped记录,因此当您引发异常时,控件直接跳转到处理程序而不是通过double
传递;但那只是一个优化。该行为与标准ML实现没有什么不同,其中double
的编译代码处理来自add
的传播异常。
我认为在这两种情况下,关键点是add
本身不能从其调用上下文中“查找”信息。相反,add
仅退出,将控制权恢复到其调用上下文。