Estado local em Common Lisp

8

Pergunta para iniciantes no Common Lisp:

Como fazer com que meu procedimento retorne objeto procedimental distinto com sua própria ligação local cada vez que for chamada? Atualmente, uso let para criar o estado local, mas duas chamadas de função estão compartilhando o mesmo estado local. Aqui está o 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

Devo fazê-lo de outra maneira? A minha maneira de escrever está errada? Alguém pode me ajudar a tirar essa dúvida? Desde já, obrigado.

netherwave
fonte
2
Observe que o Common Lisp possui um sistema de objetos, portanto, geralmente não há necessidade de modelar o estado via lambdas.
Rainer Joswig 16/11/19

Respostas:

5

Observe que, mesmo após o defunproblema -is-global ser resolvido, você precisa de muito menos máquinas do que precisa fazer algo assim. Por exemplo:

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

Então

> (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-operationé apenas uma conveniência.

tfb
fonte
5

Talvez você queira orientação a 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 criar contas com outro saldo:

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

Para mais, sugiro o livro de receitas: https://lispcookbook.github.io/cl-cookbook/clos.html

Ehvince
fonte
4

Uma regra geral é que defundeve ser usada apenas ao definir uma função no nível superior. Para definir funções locais, os dois operadores especiais flete labelspodem ser usados ​​( manual ).

Por exemplo:

(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 é como flet , mas é usado quando há definições recursivas.

Então você não precisa retornar funções dentro da função retornada por make-acc, mas nela você pode simplesmente executar a operação necessária:

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

A chamada será mais simples e retornará o 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

Por fim, se você quiser, também pode devolver duas funções diferentes para realizar depósito e retirada na conta:

(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 isso, por exemplo, como:

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

O único problema sério aqui é defunque, em comum, o lisp não é usado para definir funções locais.

Você pode, por exemplo, usar lambdas para essas operações, especialmente se desejar retornar 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)))))

Observe que eu usei, em let*vez de, letporque você precisa poder ver balancenas duas ligações a seguir.

6502
fonte