如何使用动态变量和中止控制在方案中定义异常?
How to define exceptions in scheme using a dynamic variable and abortive control?
我在使用动态范围变量和 abort
.
实现异常(handle
和 raise
)时遇到问题
这个问题来自阅读论文 A syntactic theory of dynamic binding,第 6 节图 7。
我所尝试的似乎在 try 块内抛出异常以及在 try 块内的 try 块内抛出异常时都能正常工作。
无法正常工作的是从处理程序内部抛出异常。在这种情况下,它应该中止到下一个尝试块。
你可以在这里看到我在球拍方案中的工作,以及 2 个测试程序。测试 1 正在运行,测试 2 失败。
#lang racket
;; A Syntactic Theory of Dynamic Binding - Luc Moreau
;; https://link.springer.com/content/pdf/10.1007%2FBFb0030637.pdf
(require racket/control)
(define x_ed (make-parameter 'x_ed))
; NOTE: (abort ..) is the same as (shift k ..) where you don't use k
; NOTE: in (handle f M) we call f the handler and M the try block
; v1
;(define-syntax handle
; (syntax-rules ()
; ((handle f M)
; (parameterize ((x_ed (lambda (v) (abort (f v))))) M))))
; v2
;(define-syntax handle
; (syntax-rules ()
; ((handle f M)
; (reset (parameterize ((x_ed (lambda (v) (abort (f v))))) M)))))
; v3
;(define-syntax handle
; (syntax-rules ()
; ((handle f M)
; (parameterize ((x_ed (lambda (v) (abort (f v))))) (reset M)))))
; v4
(define-syntax handle
(syntax-rules ()
((handle f M)
(let ((old-x_ed (x_ed)))
(parameterize ((x_ed (lambda (v)
(abort (parameterize ((x_ed old-x_ed))
(f v))))))
(reset M))))))
(define-syntax raise
(syntax-rules ()
((raise v) ((x_ed) v))))
(define (print x) (write x) (newline))
(define (test-1)
(print "level-1 open")
(handle (lambda (v)
(print "level-1 caught"))
(begin
(print "level-2 open")
(handle (lambda (v)
(print "level-2 caught"))
(begin
(print "level-3 open")
(raise #t)
(print "level-3 close")))
(print "level-2 close")))
(print "level-1 close"))
(define (test-2)
(print "level-1 open")
(handle (lambda (v)
(print "level-1 caught"))
(begin
(print "level-2 open")
(handle (lambda (v)
(print "level-2 caught")
(raise #t))
(begin
(print "level-3 open")
(raise #t)
(print "level-3 close")))
(print "level-2 close")))
(print "level-1 close"))
;v1
;> (test-1)
;"level-1 open"
;"level-2 open"
;"level-3 open"
;"level-2 caught"
;v2 and v3
;> (test-1)
;"level-1 open"
;"level-2 open"
;"level-3 open"
;"level-2 caught"
;"level-2 close"
;"level-1 close"
;v2 and v3
;> (test-2)
;...
;"level-2 caught"
;"level-2 caught"
; infinite loop
;v4
;> (test-2)
;"level-1 open"
;"level-2 open"
;"level-3 open"
;"level-2 caught"
;"level-1 caught"
;"level-2 close" <--- we don't want this to happen
;"level-1 close"
多亏了答案,我才能够想出这个工作版本:
(define-syntax handle
(syntax-rules ()
((handle f M)
(prompt0
(parameterize ((x_ed (lambda (v)
(control0 k (f v)))))
M)))))
(编辑:关于能够通过使用特殊的控制运算符来实现更多 space-efficient 我错了。可以使处理程序 运行 在 handle
形式的尾部位置,但我不知道有什么方法可以评估尾部位置的 body。)
首先,您是否专门尝试实现异常处理,其中 handle
表单的 body 相对于 handle
处于尾部位置形成自己?如果不是,根据简单的转义延续,有更直接的方法来实现异常处理。
如果您真的想实现 "safe for space" 或 "properly tail recursive" 异常处理,请继续阅读。
以 safe-for-space 方式实现 handle
的挑战在于,为了避免在 "no exception raised" 路径中插入额外的堆栈帧,您需要能够在该上下文中使用 expression 展开堆栈并恢复计算。或者等效地,通过在该上下文中调用过程来恢复评估。这与 call/cc
提供的不同;它只允许您展开堆栈,然后立即 return 一个值到该上下文中。
你可以用 call/cc
模拟额外的能力,代价是插入一个额外的栈帧(所以 body 不在尾部位置):
;; call/cc : ((Any -> None) -> Any) -> Any
;; call/cc/apply : (((-> Any) -> None) -> Any) -> Any
(define (call/cc/apply proc)
((call/cc (lambda (k) (let ([v (proc k)]) (lambda () v))))))
额外的堆栈帧来自 call/cc
表达式结果的应用。
你能消除对额外堆栈帧的需求吗?是的!但不是 shift
和 reset
.
您 运行 遇到的问题是 (abort e)
(其中 abort
对应于 Felleisen 和 Hieb 的 A 运算符)是 不等同于(shift _ e)
。如果您查看 docs for shift and reset,您会看到以下缩减规则:
(reset val) => val
(reset E[(shift k expr)]) => (reset ((lambda (k) expr)
(lambda (v) (reset E[v]))))
; where E has no reset
也就是shift
不去掉它的定界reset
,就是那个固执的reset
不让你跳出来您的 2 级处理程序直接到您的 1 级处理程序,而无需 运行ning (print "level-2 close")
。您需要选择一个分隔符和一个允许您删除分隔符的控制运算符。
您不能使用 reset
和 shift
。
您不能使用 prompt
和 control
.
您可以使用 prompt0
和 control0
.
您可以使用 %
和 fcontrol
(以及正确的处理程序)来完成。
当然,您可以使用 call-with-continuation-prompt
和 abort-current-continuation
(以及正确的处理程序)来完成。
我在使用动态范围变量和 abort
.
handle
和 raise
)时遇到问题
这个问题来自阅读论文 A syntactic theory of dynamic binding,第 6 节图 7。
我所尝试的似乎在 try 块内抛出异常以及在 try 块内的 try 块内抛出异常时都能正常工作。
无法正常工作的是从处理程序内部抛出异常。在这种情况下,它应该中止到下一个尝试块。
你可以在这里看到我在球拍方案中的工作,以及 2 个测试程序。测试 1 正在运行,测试 2 失败。
#lang racket
;; A Syntactic Theory of Dynamic Binding - Luc Moreau
;; https://link.springer.com/content/pdf/10.1007%2FBFb0030637.pdf
(require racket/control)
(define x_ed (make-parameter 'x_ed))
; NOTE: (abort ..) is the same as (shift k ..) where you don't use k
; NOTE: in (handle f M) we call f the handler and M the try block
; v1
;(define-syntax handle
; (syntax-rules ()
; ((handle f M)
; (parameterize ((x_ed (lambda (v) (abort (f v))))) M))))
; v2
;(define-syntax handle
; (syntax-rules ()
; ((handle f M)
; (reset (parameterize ((x_ed (lambda (v) (abort (f v))))) M)))))
; v3
;(define-syntax handle
; (syntax-rules ()
; ((handle f M)
; (parameterize ((x_ed (lambda (v) (abort (f v))))) (reset M)))))
; v4
(define-syntax handle
(syntax-rules ()
((handle f M)
(let ((old-x_ed (x_ed)))
(parameterize ((x_ed (lambda (v)
(abort (parameterize ((x_ed old-x_ed))
(f v))))))
(reset M))))))
(define-syntax raise
(syntax-rules ()
((raise v) ((x_ed) v))))
(define (print x) (write x) (newline))
(define (test-1)
(print "level-1 open")
(handle (lambda (v)
(print "level-1 caught"))
(begin
(print "level-2 open")
(handle (lambda (v)
(print "level-2 caught"))
(begin
(print "level-3 open")
(raise #t)
(print "level-3 close")))
(print "level-2 close")))
(print "level-1 close"))
(define (test-2)
(print "level-1 open")
(handle (lambda (v)
(print "level-1 caught"))
(begin
(print "level-2 open")
(handle (lambda (v)
(print "level-2 caught")
(raise #t))
(begin
(print "level-3 open")
(raise #t)
(print "level-3 close")))
(print "level-2 close")))
(print "level-1 close"))
;v1
;> (test-1)
;"level-1 open"
;"level-2 open"
;"level-3 open"
;"level-2 caught"
;v2 and v3
;> (test-1)
;"level-1 open"
;"level-2 open"
;"level-3 open"
;"level-2 caught"
;"level-2 close"
;"level-1 close"
;v2 and v3
;> (test-2)
;...
;"level-2 caught"
;"level-2 caught"
; infinite loop
;v4
;> (test-2)
;"level-1 open"
;"level-2 open"
;"level-3 open"
;"level-2 caught"
;"level-1 caught"
;"level-2 close" <--- we don't want this to happen
;"level-1 close"
多亏了答案,我才能够想出这个工作版本:
(define-syntax handle
(syntax-rules ()
((handle f M)
(prompt0
(parameterize ((x_ed (lambda (v)
(control0 k (f v)))))
M)))))
(编辑:关于能够通过使用特殊的控制运算符来实现更多 space-efficient 我错了。可以使处理程序 运行 在 handle
形式的尾部位置,但我不知道有什么方法可以评估尾部位置的 body。)
首先,您是否专门尝试实现异常处理,其中 根据简单的转义延续,有更直接的方法来实现异常处理。handle
表单的 body 相对于 handle
处于尾部位置形成自己?如果不是,
如果您真的想实现 "safe for space" 或 "properly tail recursive" 异常处理,请继续阅读。
以 safe-for-space 方式实现 handle
的挑战在于,为了避免在 "no exception raised" 路径中插入额外的堆栈帧,您需要能够在该上下文中使用 expression 展开堆栈并恢复计算。或者等效地,通过在该上下文中调用过程来恢复评估。这与 call/cc
提供的不同;它只允许您展开堆栈,然后立即 return 一个值到该上下文中。
你可以用 call/cc
模拟额外的能力,代价是插入一个额外的栈帧(所以 body 不在尾部位置):
;; call/cc : ((Any -> None) -> Any) -> Any
;; call/cc/apply : (((-> Any) -> None) -> Any) -> Any
(define (call/cc/apply proc)
((call/cc (lambda (k) (let ([v (proc k)]) (lambda () v))))))
额外的堆栈帧来自 call/cc
表达式结果的应用。
你能消除对额外堆栈帧的需求吗?是的!但不是 shift
和 reset
.
您 运行 遇到的问题是 (abort e)
(其中 abort
对应于 Felleisen 和 Hieb 的 A 运算符)是 不等同于(shift _ e)
。如果您查看 docs for shift and reset,您会看到以下缩减规则:
(reset val) => val
(reset E[(shift k expr)]) => (reset ((lambda (k) expr)
(lambda (v) (reset E[v]))))
; where E has no reset
也就是shift
不去掉它的定界reset
,就是那个固执的reset
不让你跳出来您的 2 级处理程序直接到您的 1 级处理程序,而无需 运行ning (print "level-2 close")
。您需要选择一个分隔符和一个允许您删除分隔符的控制运算符。
您不能使用 reset
和 shift
。
您不能使用 prompt
和 control
.
您可以使用 prompt0
和 control0
.
您可以使用 %
和 fcontrol
(以及正确的处理程序)来完成。
当然,您可以使用 call-with-continuation-prompt
和 abort-current-continuation
(以及正确的处理程序)来完成。