Lokaler Staat in Common Lisp

8

Neuling Frage in Common Lisp:

Wie kann ich meine Prozedur so gestalten, dass bei jedem Aufruf ein bestimmtes prozedurales Objekt mit einer eigenen lokalen Bindung zurückgegeben wird? Derzeit verwende ich let, um den lokalen Status zu erstellen, aber zwei Funktionsaufrufe verwenden denselben lokalen Status. Hier ist der Code,

(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

Soll ich es anders machen? Ist meine Schreibweise falsch? Kann mir bitte jemand helfen, diesen Zweifel auszuräumen? Danke im Voraus.

Unterwelle
quelle
2
Beachten Sie, dass Common Lisp über ein Objektsystem verfügt, sodass der Status normalerweise nicht über Lambdas modelliert werden muss.
Rainer Joswig

Antworten:

5

Beachten Sie, dass Sie selbst nach der defunLösung des globalen Problems weitaus weniger Maschinen benötigen, als Sie für so etwas benötigen. Zum Beispiel:

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

Dann

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

Offensichtlich account-operationist nur eine Annehmlichkeit.

tfb
quelle
5

Vielleicht möchten Sie Objektorientierung?

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

Verwendungszweck:

(defparameter alice-account (make-instance 'account))
(withdraw alice-account 25) ;; => 75
(balance alice-account) ;; => 75

Wir können Konten mit einem anderen Saldo erstellen:

(defparameter bob-account (make-instance 'account :balance 90))

Für mehr empfehle ich das Kochbuch: https://lispcookbook.github.io/cl-cookbook/clos.html

Ehvince
quelle
4

Eine allgemeine Regel ist, dass defunnur verwendet werden sollte, wenn eine Funktion auf oberster Ebene definiert wird. So definieren Sie lokale Funktionen , die beiden speziellen Operatoren fletund labelskann verwendet werden ( manuell ).

Zum Beispiel:

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

labelsist wie flet, aber es wird verwendet, wenn es rekursive Definitionen gibt.

Dann müssen Sie keine Funktionen innerhalb der von zurückgegebenen Funktion zurückgeben make-acc , aber darin können Sie einfach die erforderliche Operation ausführen:

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

Der Aufruf ist einfacher und gibt den erwarteten Wert zurück:

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

Wenn Sie möchten, können Sie auch zwei verschiedene Funktionen zurückgeben, um Ein- und Auszahlungen auf dem Konto durchzuführen:

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

Verwenden Sie dies zum Beispiel als:

(multiple-value-bind (paul-withdraw paul-deposit)
    (make-acc 100)
  (funcall paul-withdraw 10)
  (funcall paul-withdraw 10))
Renzo
quelle
3

Das einzige ernsthafte Problem hier ist defun dass im Allgemeinen lisp nicht zum Definieren lokaler Funktionen verwendet wird.

Sie könnten zum Beispiel lambdas für diese Operationen verwenden, insbesondere wenn Sie Lambdas zurückgeben möchten ...

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

Beachten Sie, dass ich let*anstelle von verwendet habe, letweil Sie in der Lage sein müssen, balancein den beiden folgenden Bindungen zu sehen.

6502
quelle