Estado local en Common Lisp

8

Pregunta de novato en Common Lisp:

¿Cómo hacer que mi procedimiento devuelva un objeto procesal distinto con su propio enlace local cada vez que llame? Actualmente, uso let para crear el estado local, pero dos llamadas a funciones comparten el mismo estado local. Aquí está el código

(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

¿Debería hacerlo de otra manera? ¿Está mal mi forma de escribir? ¿Alguien puede ayudarme a aclarar esta duda? Gracias por adelantado.

netherwave
fuente
2
Tenga en cuenta que Common Lisp tiene un sistema de objetos, por lo que generalmente no es necesario modelar el estado a través de lambdas.
Rainer Joswig

Respuestas:

5

Tenga en cuenta que, incluso después defunde que se aborde el problema -is-global, necesita mucha menos maquinaria de la que necesita para hacer algo como esto. Por ejemplo:

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

Entonces

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

Obviamente account-operationes solo una conveniencia.

tfb
fuente
5

Tal vez quieres orientación a los objetos?

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

Uso:

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

Podemos crear cuentas con otro saldo:

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

Para más información, sugiero el Cookbook: https://lispcookbook.github.io/cl-cookbook/clos.html

Ehvince
fuente
4

Una regla general es que defundebe usarse solo cuando se define una función en el nivel superior. Para definir las funciones locales de los dos operadores especiales flety labelspuede ser usado ( manual de ).

Por ejemplo:

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

labelses como flet, pero se usa cuando hay definiciones recursivas.

Entonces no necesita devolver funciones dentro de la función devuelta por make-acc, pero en él simplemente puede ejecutar la operación requerida:

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

La llamada será más simple y devolverá el valor esperado:

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

Finalmente, si lo desea, también puede devolver dos funciones diferentes para realizar depósitos y retiros en la cuenta:

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

usando esto por ejemplo como:

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

El único problema serio aquí es defunque en común lisp no se usa para definir funciones locales.

Por ejemplo, podría usar lambdas para esas operaciones, especialmente porque desea devolver lambdas ...

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

Tenga en cuenta que usé en let*lugar de letporque necesita poder ver balanceen los dos enlaces siguientes.

6502
fuente