首页 > 解决方案 > 如何使用动态变量和中止控制在方案中定义异常?

问题描述

我在使用动态范围的变量 and实现异常 ( handleand ) 时遇到问题。raiseabort

这个问题来自阅读论文动态绑定的句法理论,第 6 节图 7。

我所尝试的似乎可以在 try 块内引发异常以及在 try 块内的 try 块内引发异常。

不能正常工作的是从处理程序内部抛出异常。在这种情况下,它应该中止到下一个尝试块。

你可以在这里看到我在球拍计划中的工作,以及 2 个测试程序。test-1 正在工作,而 test-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)))))

标签: exceptionschemerackettheorycontinuations

解决方案


编辑:我错了通过使用特殊的控制运算符来实现更节省空间的实现。有可能使处理程序在handle表单的尾部位置运行,但我不知道有什么方法可以评估身体也在尾巴位置。)

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

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

以安全空间方式实现的挑战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/cc表达式结果的应用。

你能消除对额外堆栈帧的需要吗?是的!但不是shiftreset

您遇到的问题是(abort e)(其中abort对应于 Felleisen 和 Hieb 的A运算符)(shift _ e). 如果您查看shift 和 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 级处理程序的原因(print "level-2 close")。您需要选择一个分隔符和一个允许您删除分隔符的控制运算符。

你不能用resetand来做shift
你不能用promptand来做control
你可以用prompt0和来做control0
您可以使用%and fcontrol(以及正确的处理程序)来做到这一点。
当然,您可以使用call-with-continuation-promptand abort-current-continuation(以及正确的处理程序)来做到这一点。


推荐阅读