0

我正在编写一个需要递归分析“命令”和“程序”列表的程序(它是我们的教授为生活在迷宫中的机器人发明的某种“机器人语言”的解释器)。因为我最初的实现太慢了,所以我决定使用 call-with-current-continuation。

我知道 call/cc 是如何工作的,我已经阅读了这个这个作为解释。

我的电话/抄送是基于这部分教程:

通常我们想使用 call-with-current-continuation 来调用一些带有参数而不是转义过程的过程。例如,我们可能有一个过程,除了转义过程之外还有两个参数,因此:

(define (foo xy escape) ... (if (= x 0) (escape 'ERROR)) ...)) 我们可以通过对过程进行柯里化来解决这个问题,使其成为一个参数的过程。

[ 前面的章节应该讨论柯里化!]

假设我们想要传递 0 和 1 作为 x 和 y 的值,并将转义过程交给 foo。而不是说

(call-with-current-continuation foo) 没有将足够的参数传递给对 foo 的调用,我们说

(call-with-current-continuation (lambda (escape) (foo 0 1 escape))) lambda 表达式创建了一个完全符合我们要求的闭包。它将使用参数 0、1 和由 call-with-current-continuation 创建的转义过程调用 foo。

但是,由于某种原因它不起作用并引发此异常:

call-with-current-continuation: contract violation
  expected: (any/c . -> . any)
  given: #<procedure:...mazesimulator.ss:301:34>

我希望你能帮我找出我的错误并解释它发生的原因......

这是与此问题相关的代码部分:

; main program 
(define (simulate state expression-list program limit)

  ; read the input and set global variables
  (set! current-orientation (list-ref state 2))
  (set! current-coordinates (list-ref state 1))
  (set! current-maze (list-ref state 0))

  ; call the inner function
  (call-with-current-continuation (lambda (exit)
                                    (command state expression-list program limit exit)))
  ; this is the output
   (list list-of-executed-commands (list current-maze current-coordinates current-orientation))
  )


;; main recursive function
;; analyses expression-list parameter
;; evaluates its elements 
;; and calls itself on the cdr of the espression-list
 (define (command state expression-list program limit exit)

   (if (and (not (null? expression-list))(equal? stop-command #f))          

      ; recursion end condition, the whole procedure will be done only 
                                              ; if the list is still not empty 

      (if (atom? expression-list)              ;if the list consists of only one command 
         (if (equal? stop-command #f)          ;positive branch - if there were no erros before
             (atomic-command state expression-list program limit exit)            ;call atomic-command on this element 
             ;when flag is set to #t
             (exit))
         ; here comes a problem with "inner ifs"
         (if (atom? (car expression-list))                                         ;negative branch - if the first element is "if"
              (if (equal? (car expression-list) 'if)                               ;if the list consisits only of if-clause, no other commands ((if ...))
                    (if ((name->function (list-ref expression-list 1)))            ;evaluate the boolean - wall? north? and choose corresponding branch
                             (command state (list-ref expression-list 2) program limit exit)
                             (command state (list-ref expression-list 3) program limit exit))
                    (evaluate-first-and-call-command-on-rest expression-list program limit exit))


              (if (equal? (car(car expression-list)) 'if)                          ;if the if-clause is not the only element in list - "inner if" ((if ...) turn-left put-mark)
                  (begin                                                           ;not only evaluate if-clause,
                    (if ((name->function (list-ref (car expression-list) 1)))
                             (command state (list-ref (car expression-list) 2) program limit exit)
                             (command state (list-ref (car expression-list) 3) program limit exit))
                    (command state (cdr expression-list) program limit exit))                 ;but also call command on cdr! 
                  (evaluate-first-and-call-command-on-rest expression-list program limit exit))))

      ;when limit is exceeded or when the flag is set to #t
      (exit) ))
4

2 回答 2

2

新跟进:所以在这一点上我对发帖人的问题感到困惑。GoZoner 指出,传递的延续call/cc在调用时可能需要一个实际参数,但在 Racket 中通常不是这样(根据发帖者的错误消息,我认为这是正在讨论的语言实现)。

此外,原始发布者在问题中放置的代码片段不完整,因此无法直接执行代码以尝试复制问题。(我的非正式分析没有发现call-with-current-continuation原始发布者提出的使用中存在明显的错误。)如果原始发布者可以导出一个最小(或至少更小)的测试用例来暴露相同的问题,那将很有用。

可能是 Racket 中的一种特定语言或语言级别使用了更严格的 . call/cc,但我还没有找到这种语言级别的证据。我将向原始海报提出问题。


编辑:Chris-Jester Young 指出我的评论可能不适用于此处(请参阅对此答案的评论)。我需要更仔细地研究 Racket 在命令式代码中对延续的处理;下面的注释可能会导致提问者走上不正确的道路。(如果我能确认我下面的笔记是假的,我打算删除这个答案。)

后续编辑:看起来 Racket 的处理call/cc确实传递了一个延续,当它从丢弃值的上下文中调用时,它将接受零值。例如:

(define global 7)

(define (goner exit)
  (set! global 11)
  (exit)
  (set! global 13)
  (* 2 3))

(define (hi)
  (define x global)
  (call-with-current-continuation goner)
  (* 5 x global))

(* 5 7 11)

(hi)

以上打印385(两次);一次(* 5 7 11),一次(hi)


(原文评论如下)

您使用零参数调用的事实(exit)使我认为您并不完全了解call/cc其工作原理(如其可观察的行为),尽管您的说法相反。

call/cc我建议您在尝试将其整合到您的解决方案中之前,先玩一些完全独立于教授的机器人迷宫基础设施的小示例。

例如,我可以通过这种方式轻松地重现您的错误消息:

(define (goner)
  (* 2 3))

(define (hi)
  (let ((x (call-with-current-continuation goner)))
    (* 5 x)))

(hi)

从上面,我得到:

call-with-current-continuation: contract violation
  expected: (any/c . -> . any)
  given: #<procedure:goner>

这与您的错误消息非常相似,不是吗?(虽然老实说,这可能只是一个巧合)。

将上述运行的输出与以下运行的输出进行比较:

(define (goner exit)
  (* 2 3))

(define (hi)
  (let ((x (call-with-current-continuation goner)))
    (* 5 x)))

(hi)

和:

(define (goner exit)
  (* 2 (exit)))

(define (hi)
  (let ((x (call-with-current-continuation goner)))
    (* 5 x)))

(hi)

和:

(define (goner exit)
  (* 2 (exit 3)))

(define (hi)
  (let ((x (call-with-current-continuation goner)))
    (* 5 x)))

(hi)

和:

(define (goner exit)
  (* (exit 2) 3))

(define (hi)
  (let ((x (call-with-current-continuation goner)))
    (* 5 x)))

(hi)

仔细考虑每个说明的内容,并考虑它在您的程序中的重要性。

于 2013-05-03T00:01:34.103 回答
1

call/cc 'escape procedure' 需要一个参数。您在(exit)没有所需参数的情况下调用它。不同的 Scheme 实现会以不同的方式处理这个问题。这是一个抱怨:

1 ]=> (define (doit exit)
   (display "DOIT2\n")
   (exit))                      ;; no argument, expect error
;Value: doit

1 ]=> (define (try)
   (call-with-current-continuation
    (lambda (exit) (display "TRY\n") (doit exit)))
   'done)
;Value: try

1 ]=> (try)
TRY
DOIT2
;The procedure #[continuation 13] has been called with 0 arguments; it requires exactly 1 argument.

'escape procedure' 需要一个值的原因是call/cc,像所有的 Scheme 函数一样,需要产生一个值。提供的参数是调用转义过程时 call/cc 的返回值。

于 2013-05-03T02:12:34.180 回答