如何将一个返回闭包的Scheme函数转换为等效的Common Lisp函数?

时间:2016-05-15 22:38:03

标签: scheme common-lisp

我正在将一些Scheme代码转换为Common Lisp。我不知道Scheme。我知道一点Common Lisp。

我认为我理解这个方案代码:

(define (make-cell)
    (let ((local-name '()))
       (define (local-add-name name)
           (set! local-name name))
        (define (me message)
            (cond ((eq? message 'add-name) local-add-name)
                  ((eq? message 'name) local-name)))
     me))

使用该功能,我可以制作两个单元格:

(define a (make-cell))
(define b (make-cell))

然后我可以在每个单元格中存储一个名称:

((a 'add-name) 'a)
((b 'add-name) 'b)

然后我可以检索存储在每个单元格中的名称:

(a 'name)

=>一个

(b 'name)

=> B'/ P>

a-cell在其中存储了名称" a"。 b-cell在其中存储了名称" b"。我可以查询a-cell的名称,然后返回" a"。我可以查询b-cell的名称,然后返回" b"。

到目前为止,我的理解是否正确?

现在我想使用Common Lisp实现相同的功能。这是我创建的make-cell函数:

(defun make-cell ()
    (let ((local-name nil))
        (defun local-add-name (name)
            (setf local-name name))
        (defun me (message)
            (cond ((eq message 'add-name) #'local-add-name)
                  ((eq message 'name) local-name)))))

显然这是错误的,因为它没有给出所需的行为,正如我接下来所示。

我制作了一个a-cell和b-cell:

(setf a (make-cell))
(setf b (make-cell))

我在每个单元格中存储一个名称:

(funcall (funcall a 'add-name) 'a)
(funcall (funcall b 'add-name) 'b)

当我检索名称时,两个单元格都返回相同的名称:

(funcall a 'name)

=> B'/ P>

(funcall b 'name)

=> B'/ P>

哎哟!

为什么两个单元格都返回相同的名称?我究竟做错了什么?如何使CL代码的行为与Scheme代码相同?

3 个答案:

答案 0 :(得分:7)

Common Lisp没有像Scheme那样的内部define(顺便说一下,letrecletrec* 的语法糖。与letrec等效的Common Lisp是labels,因此您可以使用:

(defun make-cell ()
  (let (local-name)
    (labels ((local-add-name (name)
               (setf local-name name))
             (me (message)
               (ecase message
                 (add-name #'local-add-name)
                 (name local-name))))
      #'me)))

这可以按照您的预期工作(在SBCL上测试):

* (defvar *foo* (make-cell))

*FOO*
* (defvar *bar* (make-cell))

*BAR*
* (funcall (funcall *foo* 'add-name) "foo")

"foo"
* (funcall (funcall *bar* 'add-name) "bar")

"bar"
* (funcall *foo* 'name)

"foo"
* (funcall *bar* 'name)

"bar"

†以下是您的代码的letrec版本:

(define (make-cell)
  (let ((local-name #f))
    (letrec ((local-add-name (lambda (name)
                               (set! local-name name)))
             (me (lambda (message)
                   (case message
                     ((add-name) local-add-name)
                     ((name) local-name)))))
      me)))

甚至:

(define (make-cell)
  (letrec ((local-name #f)
           (local-add-name (lambda (name)
                             (set! local-name name)))
           (me (lambda (message)
                 (case message
                   ((add-name) local-add-name)
                   ((name) local-name)))))
    me))

答案 1 :(得分:5)

Chris Jester-Young已经给出了一个很好的答案,但另外你可以重新调整你的功能。请注意,这两个函数都没有按名称调用ME;你可以用LAMBDA替换它:

(defun make-cell ()
  (let (local-name)
    (flet ((add-name (name) (setf local-name name)))
      (lambda (message)
        (ecase message
          ;; ADD-NAME is only used here, so you could make it a lambda too.
          (add-name #'add-name) 
          (name local-name))))))

如果您以后需要能够按名称引用MEAlexandria会提供应该有效的宏NAMED-LAMBDA

当然,这看起来很像使用闭包实现对象。由于Common Lisp是一种多范式语言,所以最好在这里使用CLOS:

(defclass cell ()
  ((local-name :initform "" :initarg :name :reader name :writer add-name)))

(let ((cell (make-instance 'cell)))
  (format t "~&Name: ~a~%Add name: ~a~%Name: ~a~%"
          (name cell)
          (add-name "foo" cell)
          (name cell)))
; Name: 
; Add name: foo
; Name: foo

答案 2 :(得分:3)

其他答案为您的问题提供了正确的解决方案,在这里我将尝试解释为什么您的解决方案无法产生预期的结果。

在Common Lisp中,全局环境词汇环境之间存在区别。

全球环境(请参阅manual)通常会修改评估顶级表单,例如defvardefundefmacro等,例如在REPL中,并包含具有无限范围和范围的绑定。

词汇环境是一个包含以某种形式有效的信息的环境(即它是一个“本地”环境),因此在程序的每个点我们都可以谈论当前的词汇环境< / em>的。它通常通过绑定函数参数或本地定义来修改,例如let

仅用于完整性:这两种环境也与其他语言中的环境类似,但在Common Lisp中还有另一种特殊的环境,dynamic environment,但在这种情况下不感兴趣

所以,虽然defun通常位于顶层,但您可以将其放在其他形式的内部,但是无论如何,它:

  

全局环境中定义名为function-name的新函数。 ...评估defun会导致function-name成为lambda表达式指定的函数的全局名称...在执行defun的词法环境中处理。

(重点是我的)。请参阅manual

换句话说,两者您定义的函数共享相同的变量 local-name。如果您认为(defun ...)形式,情况会更清楚 函数定义的内部可以“移动”到顶层,通过与它一起移动与其中可用的变量相关的定义。由于make-cell都没有明确使用变量,因此您可以使用以下等效变换原始定义:

(let (local-name)
  (defun local-add-name (name)
    (setf local-name name))
  (defun me (message)
     (cond ((eq message 'add-name) #'local-add-name)
           ((eq message 'name) local-name)))
  (defun make-cell ()
    #'me))

这个定义应该清楚地表明这些函数完全共享相同的变量local-name。请注意,这种名为“let over lambda”的构造可能非常有用,例如,您必须在两个函数之间共享一个值,例如:

(let ((id 0))
  (defun get-new-id ()
    (incf id))
  (defun reset-id ()
    (setf id 0)))

(get-new-id) ; => 1

(get-new-id) ; => 2

(reset-id)   ; => 0

(get-new-id) ; => 1

另一方面,labelflet特殊运算符修改当前词法环境,并在每次调用封闭函数时“执行”。因此,每次调用make-cell函数时,都会生成一个 new 词法环境,其中包含 new 变量local-name,以及两个引用该变量的本地函数,返回的值是新变量上第二个函数的闭包