Common Lisp 和 Scheme 词法闭包的区别

Posted

技术标签:

【中文标题】Common Lisp 和 Scheme 词法闭包的区别【英文标题】:Differences between Common Lisp and Scheme lexical closures 【发布时间】:2013-06-24 07:26:51 【问题描述】:

在 Common Lisp 中,我可以评估以下 sn-p 代码(在 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 sn-p 代码时(在 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中完成吗?

【问题讨论】:

您需要查看 DrRacket 中 LET 的语法定义。 Common Lisp 示例是有效的,但我不会在我的代码中使用它。 我会改用 CLOS。使用上述方法会使调试更加困难,并且会阻止 DEFUN 形式被编译器识别为函数定义。在 LET 中,DEFUN 不再是***表单。 不是很明显吗? X 是 CLOS 实例中的一个插槽。那么函数就是方法。 顺便说一句,关于你正在做的事情与你可以用 CLOS 做的事情的相似性,Scheme 最初是作为一种探索面向对象思想的语言而发明的——大概使用类似于那些如下所述。 我不一定会使用 OOP 来解决具有全局变量的模块的简单问题。函数不是***表单的问题可以通过(defvar *counter* 0) 解决,函数参考*counter*。如果模块尖叫“我真的应该是一个支持多个实例化的对象,并且所有这些全局变量都应该是我的实例变量”,则适用 OOP 方法。如果您没有预料到需要多次实例化,或者没有任何继承等好处,请坚持使用全局变量。动态变量是执行全局变量的“Common Lispy”方式。 【参考方案1】:

您不能在Scheme 中定义***之外的***绑定。 (而let 的内部肯定在顶层之外——您所拥有的是内部定义,它们不会导出到顶层。)但是,使用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,这是真正的***定义发生的地方。

【讨论】:

作为老师曾经对我说过:“Scheme 是现代的 Lisp”。【参考方案2】:

在这种情况下我会使用 Chris 的解决方案,但如果您增加对 x 的操作数量,这里还有一个本着“让 Lambda 工作”精神的解决方案:

(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)

【讨论】:

【参考方案3】:

不太优雅,但非常简单:定义***变量,然后将 set!setflet 内部转换为 lambdas,具体取决于它是 Scheme 还是 CL。

【讨论】:

以上是关于Common Lisp 和 Scheme 词法闭包的区别的主要内容,如果未能解决你的问题,请参考以下文章

Common Lisp宏中的词法绑定

[lisp] scheme学习2

Common Lisp : Lexical varible , Dynamic varible ——作用域,生存期 ——environment : 绑定, 闭包与共享对象

词法作用域 vs 动态作用域

Clojure let vs Common Lisp let

Scheme 中是不是有与 Lisp 的“运行时”原语等价的东西?