是否所有异常处理都必须使用动态查找?
Does all exception handling have to use dynamic lookup?
根据 Coursera 的 "Programming Languages, Part A" 课程的 "Section 3 Summary"(华盛顿大学的 Dan Grossman):
But you have seen one feature that is more like dynamic scope than lexical scope: exception handling. When an exception is raised, evaluation has to “look up” which handle expression should be evaluated. This “look up” is done using the dynamic call stack, with no regard for the lexical structure of the program.
我觉得作者说的是Standard ML,但是C++好像也在做同样的事情。是否所有语言都"exception handling" 进行此类动态查找?
(修改后的答案)
Do all languages do "exception handling" with such dynamic lookups?
如果您将异常处理定义为始终意味着:展开调用堆栈并寻找异常处理程序,那么相似之处是不可避免的。但是“like 动态范围”和“with 动态范围”是有区别的。
顺便说一句,标准 ML 允许本地定义的异常,并且不提供 introspection 值,包括异常。例如,下面的程序不进行类型检查:
fun foo () =
let exception Foo
in bar () handle Foo => true
| _ => false
end
and bar () = raise Foo
它确实强制执行类似于动态范围的标识符查找机制的控制流,但处理程序不会继承命名异常的范围以从其父处理程序进行匹配。所以我认为说标准 ML 异常甚至与动态范围非常相似是不合理的。 dynamic programming languages 中的共同特征,如从基础 class 继承的异常和运行时类型注释使类比更强。
还有其他错误处理系统将控制流传递到别处,而不是在调用堆栈中向上传递。例如,您可以将 Erlang 的 process linking 视为一种异常处理机制,其中多个链接进程可以处理进程退出的事件。在这里,控制流只是模糊地类似于动态范围,因为没有严格的层次结构,而是异常处理程序的图形。它也不太接近异常的任何通用定义,即使进程崩溃是异常的。
是的,这就是异常处理的工作方式。
FWIW,我们今天所理解的异常处理是 70 年代在 CLU 语言中发明的,并在 80 年代初期在 ML 中得到进一步发展。它从那些传播到其他语言,如 C++,主要只是在异常的构造和匹配方式上有所变化。
还值得注意的是,异常处理只是最近发明的一种称为 effect handlers 的通用机制的特例,它更为丰富,可以表达各种其他控制结构,如协程、生成器, async/await, 甚至回溯等等。它对异常处理的主要补充是处理程序可以 恢复 抛出计算,传回一个值。与异常处理一样,它的所有应用程序都关键地依赖于处理程序的动态范围。
考虑这样一段代码:
fun add (a, b) = a + b
fun double a = add (a, b)
val _ = double 10
当add
returns时,returns控制调用堆栈上的下一个函数调用,即double
。我认为将此视为 "dynamic scope" 没有任何意义;相反,这只是调用堆栈作为调用堆栈而提供的基本功能。
类似地,当add
抛出异常时(比如Overflow
),它也会 returns控制到double
. (当然,碰巧的是,double
不处理任何异常,因此它隐式地将所有异常重新引发给它自己的调用者。)Grossman 博士显然已经想到,标准 ML 实现将保持动态- 最内层处理程序的局部范围记录,因此当您引发异常时,控件直接跳转到处理程序而不是通过 double
传递;但这只是一种优化。该行为与标准 ML 实现没有什么不同,在标准 ML 实现中,double
的编译代码处理来自 add
.
的传播异常
我认为在这两种情况下,关键点在于 add
本身 不能 "look up" 来自其调用上下文的信息。相反,add
只是 退出 ,恢复对其调用上下文的控制。
根据 Coursera 的 "Programming Languages, Part A" 课程的 "Section 3 Summary"(华盛顿大学的 Dan Grossman):
But you have seen one feature that is more like dynamic scope than lexical scope: exception handling. When an exception is raised, evaluation has to “look up” which handle expression should be evaluated. This “look up” is done using the dynamic call stack, with no regard for the lexical structure of the program.
我觉得作者说的是Standard ML,但是C++好像也在做同样的事情。是否所有语言都"exception handling" 进行此类动态查找?
(修改后的答案)
Do all languages do "exception handling" with such dynamic lookups?
如果您将异常处理定义为始终意味着:展开调用堆栈并寻找异常处理程序,那么相似之处是不可避免的。但是“like 动态范围”和“with 动态范围”是有区别的。
顺便说一句,标准 ML 允许本地定义的异常,并且不提供 introspection 值,包括异常。例如,下面的程序不进行类型检查:
fun foo () =
let exception Foo
in bar () handle Foo => true
| _ => false
end
and bar () = raise Foo
它确实强制执行类似于动态范围的标识符查找机制的控制流,但处理程序不会继承命名异常的范围以从其父处理程序进行匹配。所以我认为说标准 ML 异常甚至与动态范围非常相似是不合理的。 dynamic programming languages 中的共同特征,如从基础 class 继承的异常和运行时类型注释使类比更强。
还有其他错误处理系统将控制流传递到别处,而不是在调用堆栈中向上传递。例如,您可以将 Erlang 的 process linking 视为一种异常处理机制,其中多个链接进程可以处理进程退出的事件。在这里,控制流只是模糊地类似于动态范围,因为没有严格的层次结构,而是异常处理程序的图形。它也不太接近异常的任何通用定义,即使进程崩溃是异常的。
是的,这就是异常处理的工作方式。
FWIW,我们今天所理解的异常处理是 70 年代在 CLU 语言中发明的,并在 80 年代初期在 ML 中得到进一步发展。它从那些传播到其他语言,如 C++,主要只是在异常的构造和匹配方式上有所变化。
还值得注意的是,异常处理只是最近发明的一种称为 effect handlers 的通用机制的特例,它更为丰富,可以表达各种其他控制结构,如协程、生成器, async/await, 甚至回溯等等。它对异常处理的主要补充是处理程序可以 恢复 抛出计算,传回一个值。与异常处理一样,它的所有应用程序都关键地依赖于处理程序的动态范围。
考虑这样一段代码:
fun add (a, b) = a + b
fun double a = add (a, b)
val _ = double 10
当add
returns时,returns控制调用堆栈上的下一个函数调用,即double
。我认为将此视为 "dynamic scope" 没有任何意义;相反,这只是调用堆栈作为调用堆栈而提供的基本功能。
类似地,当add
抛出异常时(比如Overflow
),它也会 returns控制到double
. (当然,碰巧的是,double
不处理任何异常,因此它隐式地将所有异常重新引发给它自己的调用者。)Grossman 博士显然已经想到,标准 ML 实现将保持动态- 最内层处理程序的局部范围记录,因此当您引发异常时,控件直接跳转到处理程序而不是通过 double
传递;但这只是一种优化。该行为与标准 ML 实现没有什么不同,在标准 ML 实现中,double
的编译代码处理来自 add
.
我认为在这两种情况下,关键点在于 add
本身 不能 "look up" 来自其调用上下文的信息。相反,add
只是 退出 ,恢复对其调用上下文的控制。