有没有熟悉scheme的大佬?帮忙解答一个scheme标准的问题

为了测试 let 的语义是先求值后绑定, letrec 是先绑定 (分配空间) 后求值, 我写了下面这样的代码:

(let ([x (call/cc (lambda (kk)
                    (set! k kk)
                    0))])
  (set! f
    (lambda ()
      (set! x (+ x 1))
      x)))
(k 0)
(set! g f)
(k 0)
"--- begin let ---"
(g)
(f)
(g)
(f)
(g)
(f)

(letrec ([x (call/cc (lambda (kk)
                       (set! k kk)
                       0))])
  (set! f
    (lambda ()
      (set! x (+ x 1))
      x)))
(k 0)
(set! g f)
(k 0)
"--- begin letrec ---"
(g)
(f)
(g)
(f)
(g)
(f)

但是我在 chez 里面跑, letrec 竟然不是输出123456, 而我测了 Racket 和一些其他实现都是123456. 然后我去查阅 R6RS, 看到在 letrec 的条目那里写的是:

Implementations may or may not detect that the continuation of each init is
invoked more than once. However, if the implementation detects this, it must
raise an exception with condition type &assertion.

这里没有说如果不报错的话, 行为还是否要求和标准一致. 所以这里的原因是 chez 认为这个是类似 C++ 的"未定义行为"所以利用这一点做了一些优化吗?

和标准怎么定义 call/cc 行为没关系。

其实是这样的,Chez 看见你这个 letrec 实际上并没有递归调用,就给你优化成和普通 let 一样的行为了

1 个赞

不是callcc的行为,是letrec的行为。包括kent编译器的课也特意提到letrec转成let这个行为是有可能被callcc捕捉到的,所以不一定sound。

你引用那段只是说 detect if continuation of each <init> is invoked more than once.

得细看 R6RS Appendix A.11 关于 letrec 语义的部分,重点在 (reinit ...) 的行为。

If it is already #t, then reinit either just does nothing, or it raises an exception.

再结合 Fig A.11 的语义看。

反正我怎么看怎么觉得,Racket 的实现是完全符合 6reinit 的。

而 Chez 实际上就是完全没鸟标准怎么说。

所以

错误的,Chez 就是没照 R6RS 标准来而已。


Chez 用 expand/optimize 可以看到

(expand/optimize '(letrec ([x (call/cc (lambda (kk)
                         (set! k kk)
                         0))])
    (set! f
      (lambda ()
        (set! x (+ x 1))
        x))))
(let ([#{x h2vz11n46n2u660sch3tnn4dm-0} (#2%call/cc
                                          (lambda (#{kk h2vz11n46n2u660sch3tnn4dm-1})
                                            (set! k
                                              #{kk h2vz11n46n2u660sch3tnn4dm-1})
                                            0))])
  (set! f
    (lambda ()
      (set! #{x h2vz11n46n2u660sch3tnn4dm-0}
        (#2%+ 1 #{x h2vz11n46n2u660sch3tnn4dm-0}))
      #{x h2vz11n46n2u660sch3tnn4dm-0})))

感谢,我没注意到附录里更加详细的语义。不过还是很惊讶chez是不合标准的

Chez Scheme 这么做其实是有一定依据的,因为你用来测试的程序也不是符合 R6RS 的「潜规则」

R6RS 中写了

Another restriction is that the continuation of each <init> should not be invoked more than once.

而 Chez 也没加入标准里规定应当加入的檢查,只是领会精神了默默就做了到 let 的转換。而 Racket 是照著规范语义实现的。