net*_*ave 8 lisp state common-lisp let
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
Run Code Online (Sandbox Code Playgroud)
我应该用其他方式吗?我的写作方式有误吗?有人可以帮我解决这个疑问吗?提前致谢。
请注意,即使在处理了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))
Run Code Online (Sandbox Code Playgroud)
然后
> (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
Run Code Online (Sandbox Code Playgroud)
显然account-operation只是一种方便。
也许您想要面向对象?
(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))
Run Code Online (Sandbox Code Playgroud)
用法:
(defparameter alice-account (make-instance 'account))
(withdraw alice-account 25) ;; => 75
(balance alice-account) ;; => 75
Run Code Online (Sandbox Code Playgroud)
我们可以创建另一个余额的帐户:
(defparameter bob-account (make-instance 'account :balance 90))
Run Code Online (Sandbox Code Playgroud)
有关更多信息,我建议使用菜谱:https : //lispcookbook.github.io/cl-cookbook/clos.html
一般规则是defun仅应在顶层定义函数时使用。要定义局部函数,可以使用两个特殊运算符flet和(手动)。labels
例如:
(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))))))))
Run Code Online (Sandbox Code Playgroud)
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)))))))
Run Code Online (Sandbox Code Playgroud)
调用会更简单,并且会返回预期值:
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
Run Code Online (Sandbox Code Playgroud)
最后,如果您愿意,您还可以返回两个不同的函数来对帐户执行存款和取款:
(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))))
Run Code Online (Sandbox Code Playgroud)
例如使用它:
(multiple-value-bind (paul-withdraw paul-deposit)
(make-acc 100)
(funcall paul-withdraw 10)
(funcall paul-withdraw 10))
Run Code Online (Sandbox Code Playgroud)