如何使用动态变量和异常控制来定义方案中的异常?

时间:2018-10-22 23:08:35

标签: exception scheme racket theory continuations

我无法使用动态范围内的变量和handle实现异常(raiseabort)。

这个问题来自阅读论文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)))))

1 个答案:

答案 0 :(得分:5)

编辑:我错在能够通过使用特殊的控制运算符来实现更节省空间的实现。这可能使处理程序相对于{{1 }}的形式,但我也不知道用哪种方法来评估尾巴位置的身体。)

首先,您是否专门尝试在handle表单的主体相对于handle表单本身处于尾部的位置实现异常处理?如果不是这样,根据简单的转义连续性,还有更简单的方法来实现异常处理。

但是,如果您确实要实现“空间安全”或“适当地尾部递归”异常处理,请继续阅读。

以安全的空间方式实现handle的挑战在于,要避免在“无异常引发”路径中插入额外的堆栈框架,您需要能够展开堆栈并在这种情况下使用 expression 恢复评估。或者等效地,通过在该上下文中调用过程来恢复评估。这与handle提供的内容有所不同;它只允许您展开堆栈,然后立即将一个值返回到该上下文中。

您可以使用call/cc模拟额外的力量,但要以插入额外的堆叠框架为代价(因此,车身不会处于尾部位置):

call/cc

额外的堆栈帧来自;; 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/ccshift

您遇到的问题是reset(其中(abort e)对应于Felleisen,而Hieb的 A 运算符)与{ {1}}。如果您查看docs for shift and reset,则会看到以下缩减规则:

abort

也就是说,(shift _ e)不会删除定界符(reset val) => val (reset E[(shift k expr)]) => (reset ((lambda (k) expr) (lambda (v) (reset E[v])))) ; where E has no reset ,而顽固的​​shift是阻止您跳出二级句柄的原因直接转到您的1级处理程序,而无需运行reset。您需要选择一个定界符和一个允许删除定界符的控制运算符。

您无法使用reset(print "level-2 close")来做到这一点。
您无法使用resetshift来做到这一点。
您可以使用promptcontrol来做到这一点。
您可以使用prompt0control0(以及正确的处理程序)来完成此操作。
当然,您可以使用%fcontrol(以及正确的处理程序)来做到这一点。