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

时间:2016-02-10 15:23:16

标签: lisp common-lisp

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

(defun make-up-counter ()
  (let ((n 0))
    #'(lambda () (incf n))))

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

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

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

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

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

CL-USER > (funcall up2)
3

这是我最好的尝试:

(defun make-up-counter ()
  (let ((n 0))
    #'(lambda (&optional copy)
        (if (null copy)
            (incf n)
          (let ((n 0))
            #'(lambda () (incf n)))))))

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

调用它
(defun copy-counter (counter) (funcall counter t))

适用于第一代副本:

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

CL-USER > (funcall up2)
1

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

有什么建议吗?

6 个答案:

答案 0 :(得分:8)

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

(defun make-up-counter ()
  (let ((n 0))
    #'(lambda () (incf n))))

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

问题:

CL-USER 36 > (make-up-counter)
#<anonymous interpreted function 40600015BC>

这是什么?这是一个像所有其他人一样的功能。它没有说明它的目的,它的论点,文档,来源,没有文档化的界面,没有有用的印刷表示,代码在使用时无法更新,......我们可以在其中添加更多功能 - 内部 - 但是我们可以从像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))
  ...)

...

然后:

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

答案 1 :(得分:7)

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

(defun make-up-counter ()
  (labels ((new ()
             (let ((n 0))
               (lambda (&optional copy)
                 (if copy
                     (new)
                     (incf n))))))
    (new)))

copy为真时,您甚至可以复制当前计数器值:

(defun make-up-counter ()
  (labels ((new (n)
             (lambda (&optional copy)
               (if copy
                   (new n)
                   (incf n)))))
    (new 0)))

两个世界中最好的,如果copy是数字,你可以创建一个具有指定值的计数器,否则只要复制计数器值,否则增加:

(defun make-up-counter ()
  (labels ((new (n)
             (lambda (&optional copy)
               (cond ((numberp copy) (new copy))
                     (copy (new n))
                     (t (incf n))))))
    (new 0)))

答案 2 :(得分:1)

Rainer's answer是正确的,你应该避免使用闭包作为对象。您可以使用已显示的Price,但对于简单的计数器,您只需编写:

defclass

这定义了您所需要的一切:(defstruct counter (value 0)) (make-counter)(make-counter :value x)都按预期工作。您的对象也可以可读打印。

(copy-counter c)

您仍然应该导出更高级别的功能,例如重置下一步,这样您的计数器用户就不需要知道它是如何实现的。

答案 3 :(得分:0)

这是另一种解决方案,其中copy-counter获取一个计数器作为参数,并从参数的当前值开始返回一个新计数器,但使用另一个变量:

(defun new-counter(&optional (n 0))
  (lambda (&optional noincrement)
    (if noincrement n (incf n))))

(defun copy-counter(c)
  (new-counter (funcall c t)))

这是一个测试:

CL-USER> (let* ((up1 (new-counter))
                (up2 (progn (funcall up1) (funcall up1) (copy-counter up1))))
           (print (funcall up2))
           (print (funcall up2))
           (print (funcall up2))
           (print (funcall up1))
           "end test")

3 
4 
5 
3 
"end test"

答案 4 :(得分:0)

解决此问题的最简单方法是make-up-counter取一个数字来计算。

(defun make-up-counter (&optional (initial-count 0))
  (let ((count initial-count))
    #'(lambda () (incf count))))

然而,这并没有解决问题,因为我们无法检查计数器中的值,因此如果我们尝试使用计数器来设置值,我们将修改当前计数器。

(defun copy-counter (counter)
  (make-up-counter (funcall counter)))

为了提供一种方法来获得我们所做的价值,闭包采取了一个&#39;操作&#39;参数,所以我们可以检查或增加值

(defun make-up-counter (&optional (initial-count 0))
  (let ((count initial-count))
    #'(lambda (&optional (operation :increment))
        (ecase operation
          (:inspect count)
          (:increment (incf count))))))

(defun copy-counter (counter)
  (make-up-counter (funcall counter :inspect)))

现在我们运行以下代码

(let ((1st-counter (make-up-counter)))
  (loop :repeat 3 :do (funcall 1st-counter))
  (let ((2nd-counter (copy-counter 1st-counter)))
    (loop :repeat 3 :do (funcall 1st-counter))
    (format t "1st counter: ~A~%2nd counter: ~A~%"
            (funcall 1st-counter :inspect)
            (funcall 2nd-counter :inspect))))

打印

  

第一个柜台:6

     

第二个柜台:3

答案 5 :(得分:0)

我们可以做的是定义用于构建计数器的API,以便:

(make-counter <integer>) --> yields new counter starting at <integer>
(make-counter <counter>) --> yields a clone of counter

诀窍是我们还给计数函数(计数器本身)一个可选参数。如果该参数是nil,那么它只是重要的。否则它会克隆自己。这只是基本的OOP:计数器是一个对象,它有两种方法。当要求make-counter API克隆计数器时,它会委托复制&#34;方法&#34;。

(defun make-counter (&optional (constructor-arg 0))
  (etypecase constructor-arg
    (integer 
      (lambda (&optional opcode) ;; opcode selects method
        (ecase opcode ;; method dispatch
          ((nil) (prog1 constructor-arg (incf constructor-arg)))
          (copy-self (make-counter constructor-arg)))))
    (function
      (funcall constructor-arg 'copy-self))))

试运行:

[1]> (defvar x (make-counter 3))
X
[2]> (funcall x)
3
[3]> (funcall x)
4
[4]> (defvar y (make-counter x))
Y
[5]> (funcall x)
5
[6]> (funcall x)
6
[7]> (funcall x)
7
[8]> (funcall y)
5
[9]> (funcall y)
6
[10]> (funcall x)
8
[11]> (funcall y)
7

我们可以将它分成双功能API,而不是以这种方式重载make-counter构造函数:

(defun make-counter (&optional (constructor-arg 0))
  (lambda (&optional opcode)
    (ecase opcode
      ((nil) (prog1 constructor-arg (incf constructor-arg)))
      (copy-self (make-counter constructor-arg)))))

(defun copy-counter (counter)
  (funcall constructor-arg 'copy-self))

copy-counter只不过是对象上基于操作码的低级调度API的包装器。