Common Lisp中的新手问题:
如何使我的过程每次调用时都返回具有自己的本地绑定的不同过程对象?当前,我使用let创建本地状态,但是两个函数调用共享相同的本地状态。这是代码,
(defun make-acc ()
(let ((balance 100))
(defun withdraw (amount)
(setf balance (- balance amount))
(print balance))
(defun deposit (amount)
(setf balance (+ balance amount))
(print balance))
(lambda (m)
(cond ((equal m 'withdraw)
(lambda (x) (withdraw x)))
((equal m 'deposit)
(lambda (x) (deposit x)))))))
;; test
(setf peter-acc (make-acc))
(setf paul-acc (make-acc))
(funcall (funcall peter-acc 'withdraw) 10)
;; Give 90
(funcall (funcall paul-acc 'withdraw) 10)
;; Expect 90 but give 80
我应该用其他方式吗?我的写作方式有误吗?有人可以帮我解决这个疑问吗?预先感谢。
答案 0 :(得分:5)
请注意,即使在解决defun
-is-global问题之后,与执行类似操作相比,所需的机械设备也要少得多。例如:
(defun make-account (balance)
(lambda (op amount)
(ecase op
((withdraw)
(decf balance amount))
((deposit)
(incf balance amount)))))
(defun account-operation (account op &rest args-to-op)
(apply account op args-to-op))
然后
> (setf joe-acct (make-account 10))
#<Closure 1 subfunction of make-account 4060010B54>
> (setf mary-acct (make-account 100))
#<Closure 1 subfunction of make-account 4060010C94>
> (account-operation joe-acct 'withdraw 10)
0
> (account-operation mary-acct 'deposit 10)
110
显然account-operation
只是一种方便。
答案 1 :(得分:5)
也许您要面向对象?
(defclass account ()
((balance :initarg :balance
:initform 100
:accessor balance)))
(defmethod withdraw ((acc account) amount)
(decf (balance acc) amount))
(defmethod deposit ((acc account) amount)
(incf (balance acc) amount))
用法:
(defparameter alice-account (make-instance 'account))
(withdraw alice-account 25) ;; => 75
(balance alice-account) ;; => 75
我们可以创建另一个余额的帐户:
(defparameter bob-account (make-instance 'account :balance 90))
有关更多信息,我建议菜谱:https://lispcookbook.github.io/cl-cookbook/clos.html
答案 2 :(得分:4)
一般规则是defun
仅在定义顶层函数时才应使用。要定义局部函数,可以使用两个特殊运算符flet
和labels
(manual)。
例如:
(defun make-acc ()
(let ((balance 100))
(flet ((withdraw (amount)
(setf balance (- balance amount))
(print balance))
(deposit (amount)
(setf balance (+ balance amount))
(print balance)))
(lambda (m)
(cond ((equal m 'withdraw)
(lambda (x) (withdraw x)))
((equal m 'deposit)
(lambda (x) (deposit x))))))))
labels
类似于flet
,但是在有递归定义时使用。
然后,您无需在make-acc
返回的函数内部返回函数,但是您可以在其中简单地执行所需的操作:
(defun make-acc ()
(let ((balance 100))
(flet ((withdraw (amount)
(setf balance (- balance amount))
(print balance))
(deposit (amount)
(setf balance (+ balance amount))
(print balance)))
(lambda (m x)
(cond ((equal m 'withdraw)
(withdraw x))
((equal m 'deposit)
(deposit x)))))))
该调用将更加简单,并将返回预期值:
CL-USER> (setf paul-acc (make-acc))
#<CCL:COMPILED-LEXICAL-CLOSURE (:INTERNAL MAKE-ACC) #x3020021640AF>
CL-USER> (funcall paul-acc 'withdraw 10)
90
90
CL-USER> (funcall paul-acc 'withdraw 10)
80
80
最后,如果需要,您还可以返回两个不同的功能来对帐户进行存款和取款:
(defun make-acc (initial-amount)
(let ((balance initial-amount))
(flet ((withdraw (amount)
(setf balance (- balance amount))
(print balance))
(deposit (amount)
(setf balance (+ balance amount))
(print balance)))
(values #'withdraw #'deposit))))
将此作为例如:
(multiple-value-bind (paul-withdraw paul-deposit)
(make-acc 100)
(funcall paul-withdraw 10)
(funcall paul-withdraw 10))
答案 3 :(得分:3)
这里唯一严重的问题是defun
,在常见的lisp中,它不用于定义局部函数。
例如,您可以使用lambda
来执行这些操作,尤其是当您要返回lambda时...
(defun make-acc ()
(let* ((balance 100)
(withdraw (lambda (amount)
(setf balance (- balance amount))
(print balance)))
(deposit (lambda (amount)
(setf balance (+ balance amount))
(print balance))))
(lambda (m)
(cond
((equal m 'withdraw) withdraw)
((equal m 'deposit) deposit)))))
请注意,我使用let*
代替了let
,因为您需要能够在以下两个绑定中看到balance
。