如何复制使用Lisp闭包制作的计数器?

joh*_*ies 5 lisp common-lisp

Lisp闭包的经典示例是以下返回计数器的函数:

(defun make-up-counter ()
  (let ((n 0))
    #'(lambda () (incf n))))
Run Code Online (Sandbox Code Playgroud)

调用时,它会递增计数并返回结果:

CL-USER > (setq up1 (make-up-counter))
#<Closure 1 subfunction of MAKE-UP-COUNTER 20099D9A>

CL-USER > (funcall up1)
1

CL-USER > (funcall up1)
2
Run Code Online (Sandbox Code Playgroud)

当我向一个不熟悉Lisp的朋友展示这个时,他问我如何复制一个计数器来创建一个新的,相同类型的独立计数器.这不起作用:

CL-USER > (setq up2 up1)
#<Closure 1 subfunction of MAKE-UP-COUNTER 20099D9A>
Run Code Online (Sandbox Code Playgroud)

因为up2不是一个新的计数器,它只是同一个计数器的另一个名称:

CL-USER > (funcall up2)
3
Run Code Online (Sandbox Code Playgroud)

这是我最好的尝试:

(defun make-up-counter ()
  (let ((n 0))
    #'(lambda (&optional copy)
        (if (null copy)
            (incf n)
          (let ((n 0))
            #'(lambda () (incf n)))))))
Run Code Online (Sandbox Code Playgroud)

要返回计数器的副本,请使用参数t调用它:

(defun copy-counter (counter) (funcall counter t))
Run Code Online (Sandbox Code Playgroud)

它适用于第一代副本:

CL-USER > (setq up2 (copy-counter up1))
#<Closure 1 subfunction of MAKE-UP-COUNTER 200DB722>

CL-USER > (funcall up2)
1
Run Code Online (Sandbox Code Playgroud)

但是如果你试图复制up2,它显然是行不通的.我无法看到如何让它正常工作,因为化妆计数器的定义需要在自己的定义中有自己的副本.

有什么建议?

Rai*_*wig 8

没有真正回答这个问题.但是这样,副本会更容易......

(defun make-up-counter ()
  (let ((n 0))
    #'(lambda () (incf n))))
Run Code Online (Sandbox Code Playgroud)

通常,我会避免在可维护软件中使用此类代码的更复杂版本以供生产使用.调试和内省更难.它是过去基本的FP知识(使用闭包隐藏可变状态,例如参见早期的Scheme文件),但对于任何更复杂的东西来说,这是一种痛苦.它隐藏了值 - 这是有用的 - 但同时它使调试变得困难.Minimum是一个能够查看闭包绑定的调试器/检查器.它很方便,因为它很容易编写,但价格会在以后支付.

问题:

CL-USER 36 > (make-up-counter)
#<anonymous interpreted function 40600015BC>
Run Code Online (Sandbox Code Playgroud)

它是什么?这是一个像所有其他人一样的功能.它没有说明它的目的,它的论点,文档,来源,没有文档化的界面,没有有用的印刷表示,代码在使用时无法更新,......我们可以在其中添加更多功能 - 内部 - 但是我们可以从像CLOS这样的对象系统中免费获得所有这些.

(defclass counter ()
  ((value :initarg :start :initform 0 :type integer)))

(defmethod next-value ((c counter))
   (with-slots (value) c
     (prog1 value
       (incf value))))

(defmethod copy-counter ((c counter))
  ...)

(defmethod reset-counter ((c counter))
  ...)

...
Run Code Online (Sandbox Code Playgroud)

然后:

CL-USER 44 > (let ((c (make-instance 'counter :start 10)))
               (list (next-value c)
                     (next-value c)
                     (next-value c)
                     c))
(10 11 12 #<COUNTER 40200E6F3B>)

CL-USER 45 > (describe (fourth *))

#<COUNTER 40200E6F3B> is a COUNTER
VALUE      13
Run Code Online (Sandbox Code Playgroud)


Chr*_*ung 7

要解决此问题,您需要使用递归函数,使用labels:

(defun make-up-counter ()
  (labels ((new ()
             (let ((n 0))
               (lambda (&optional copy)
                 (if copy
                     (new)
                     (incf n))))))
    (new)))
Run Code Online (Sandbox Code Playgroud)

如果copy为true,您甚至可以复制当前计数器值:

(defun make-up-counter ()
  (labels ((new (n)
             (lambda (&optional copy)
               (if copy
                   (new n)
                   (incf n)))))
    (new 0)))
Run Code Online (Sandbox Code Playgroud)

为了两全其美,你可以创建一个具有指定值的计数器(如果copy是数字),否则只要复制计数器值,如果是真的,否则增加:

(defun make-up-counter ()
  (labels ((new (n)
             (lambda (&optional copy)
               (cond ((numberp copy) (new copy))
                     (copy (new n))
                     (t (incf n))))))
    (new 0)))
Run Code Online (Sandbox Code Playgroud)