Local State in Common Lisp
Asked Answered
A

4

8

Newbie question in Common Lisp:

How to make my procedure to return distinct procedural object with its own local binding each time call? Currently, I use let to create the local state, but two function calls are sharing the same local state. Here is the 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

Should I do it in another way? Is my way of writing wrong? Can someone pls help me to clear this doubt? Thanks in advance.

Anglice answered 16/11, 2019 at 4:51 Comment(1)
Note that Common Lisp has an object system, so there is usually no need to model state via lambdas.Hendecahedron
B
6

Note that, even after the defun-is-global problem is dealt with, you need far less machinery than you have to do something like this. For instance:

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

Then

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

Obviously account-operation is just a convenience.

Beaker answered 16/11, 2019 at 8:9 Comment(0)
C
6

Maybe you want object orientation?

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

Usage:

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

We can create accounts with another balance:

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

For more, I suggest the Cookbook: https://lispcookbook.github.io/cl-cookbook/clos.html

Calvin answered 16/11, 2019 at 13:36 Comment(0)
Q
4

A general rule is that defun should be used only when defining a function at top level. To define local functions the two special operators flet and labels can be used (manual).

For instance:

(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 is like flet, but it is used when there are recursive definitions.

Then you don't need to return functions inside the function returned by make-acc, but in it you can simply execute the required operation:

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

The call will be more simple and will return the expected value:

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

Finally, if you want you can also return two different functions to perform deposit and withdrawal on the account:

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

using this for instance as:

(multiple-value-bind (paul-withdraw paul-deposit)
    (make-acc 100)
  (funcall paul-withdraw 10)
  (funcall paul-withdraw 10))
Quarterage answered 16/11, 2019 at 7:10 Comment(0)
T
3

The only serious problem here is defun that in common lisp is not used to define local functions.

You could for example use lambdas for those operations, especially as you want to return 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)))))

Note that I used let* instead of let because you need to be able to see balance in the two following bindings.

Tournai answered 16/11, 2019 at 7:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.