我正在将一些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代码相同?
答案 0 :(得分:7)
Common Lisp没有像Scheme那样的内部define
(顺便说一下,letrec
或letrec*
†的语法糖。与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))))))
如果您以后需要能够按名称引用ME
,Alexandria会提供应该有效的宏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)通常会修改评估顶级表单,例如defvar
,defun
,defmacro
等,例如在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
另一方面,label
和flet
特殊运算符修改当前词法环境,并在每次调用封闭函数时“执行”。因此,每次调用make-cell
函数时,都会生成一个 new 词法环境,其中包含 new 变量local-name
,以及两个引用该变量的本地函数,返回的值是新变量上第二个函数的闭包。