Common Lisp和Scheme词法闭包的区别

3
在Common Lisp中,我可以在SBCL中评估以下代码片段,而不会收到任何语法错误的信号:
(let ((x 0))
   (defun my-incf (y)
     (setf x (+ x y)))
   (defun my-decf (y)
     (setf x (- x y))))
MY-DECF

CL-USER> (my-incf 1)
1
CL-USER> (my-incf 1)
2
CL-USER> (my-decf 1)
1
CL-USER> (my-decf 1)
0

当我尝试评估一个相应的Scheme代码片段(在DrRacket中)时:

(let ((x 0))
  (define (my-incf y)
    (set! x (+ x y)))
  (define (my-decf y)
    (set! x (- x y))))

这表示出现了语法错误。

begin (possibly implicit): no expression after a sequence of internal definitions in: (begin (define (my-incf y) (set! x (+ x y))) (define (my-decf y) (set! x (- x y))))

有人知道为什么这在Scheme中无法实现吗?

1
你需要查看DrRacket中LET的语法定义。 Common Lisp的示例是有效的,但我不会在我的代码中使用它。 - Rainer Joswig
1
我会使用CLOS代替。使用上述方法会使调试更加困难,并且它会防止编译器将DEFUN表单识别为函数定义。在LET内部,DEFUN不再是顶级表单。 - Rainer Joswig
3
很明显吧?X是CLOS实例中的一个插槽,那么这些函数就是方法。 - Rainer Joswig
1
顺便提一下,关于你所做的事情与使用CLOS可以做的事情的相似之处,Scheme最初是作为一种语言发明的,用于探索面向对象的思想--可能使用类似于下面描述的技术。 - Mars
1
我不会一定使用面向对象编程来解决一个带有全局变量的模块的简单问题。函数不是顶层形式的问题可以通过(defvar *counter* 0)来解决,并让函数引用*counter*。如果该模块需要支持多个实例并且所有这些全局变量应该是其实例变量,则可以采用面向对象的方法。如果您不预计需要多个实例或任何继承等好处,请使用全局变量。动态变量是处理全局变量的“Common Lispy”方式。 - Kaz
显示剩余3条评论
3个回答

10

在Scheme中,您无法在top-level之外定义top-level绑定。 (而在let内部肯定是在top-level之外---您实际上是在使用不会导出到top-level的内部定义。)但是,通过使用define-values,您仍然可以完成所需的操作:

(define-values (my-incf my-decf)
  (let ((x 0))
    (values (lambda (y)
              (set! x (+ x y))
              x)
            (lambda (y)
              (set! x (- x y))
              x))))

然而,你仍然可以使用内部定义来使你的代码更易读:

(define-values (my-incf my-decf)
  (let ((x 0))
    (define (my-incf y)
      (set! x (+ x y))
      x)
    (define (my-decf y)
      (set! x (- x y))
      x)
    (values my-incf my-decf)))

两全其美。:-) 在这种情况下,values 将内部的 my-incfmy-decf 定义发送到外部的 define-values,真正的顶层定义发生在此处。


1
曾经有一位老师对我说过:“Scheme是现代Lisp”。 - Paulo Tomé

3

Chris的解决方案是我在这种情况下会使用的,但以下是另一种方法,它符合“Let over Lambda”的精神,如果您增加了对x的操作数量,则可能会很方便:

(define inc-dec
  (let ((x 0))
    (lambda (msg y)
      (case msg
        ((incf) (set! x (+ x y)))
        ((decf) (set! x (- x y)))
        (else (error "wot?")))
      x)))


(inc-dec 'incf 1)
(inc-dec 'incf 1)
(inc-dec 'decf 1)
(inc-dec 'decf 1)

0
较不优美,但非常简单:定义顶层变量,然后从let内部使用set!setf将它们设置为lambda,具体取决于它是Scheme还是CL。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接