Common Lispのローカル状態


8

Common Lispの初心者の質問:

呼び出すたびに独自のローカルバインディングを持つ個別の手続き型オブジェクトを返すようにプロシージャを作成するにはどうすればよいですか?現在、letを使用してローカルステートを作成していますが、2つの関数呼び出しが同じローカルステートを共有しています。これがコードです

(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

別の方法で行う必要がありますか?私の書き方は間違っていますか?誰かが私にこの疑問を解決するのを手伝ってくれる?前もって感謝します。


2
Common Lispにはオブジェクトシステムがあるため、通常はラムダを介して状態をモデル化する必要はありません。
Rainer Joswig、

回答:


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便利なだけです。


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


4

一般的なルールはdefun、関数をトップレベルで定義する場合にのみ使用することです。ローカル関数を定義するには、2つの特別な演算子fletlabels使用できます(手動)。

例えば:

(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

最後に、必要に応じて2つの異なる関数を返して、アカウントで預金と引き出しを実行することもできます。

(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

ここでの唯一の深刻な問題は defun、一般的にlispがローカル関数の定義に使用されていないことです。

たとえばlambdaラムダを返したい場合は特に、これらの操作にsを使用できます...

(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)))))

次の2つのバインディングで表示できるようにする必要let*があるletため、代わりに使用したことに注意してくださいbalance

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.