Nhà nước địa phương ở Lisp chung


8

Câu hỏi của người mới trong Lisp chung:

Làm thế nào để thực hiện thủ tục của tôi để trả về đối tượng thủ tục khác biệt với ràng buộc cục bộ của chính nó mỗi lần gọi? Hiện tại, tôi sử dụng let để tạo trạng thái cục bộ, nhưng hai lệnh gọi hàm đang chia sẻ cùng một trạng thái cục bộ. Đây là mã

(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

Tôi có nên làm theo cách khác? Là cách viết của tôi sai? Ai đó có thể giúp tôi để xóa bỏ nghi ngờ này? Cảm ơn trước.


2
Lưu ý rằng Common Lisp có một hệ thống đối tượng, do đó thường không cần mô hình hóa trạng thái thông qua lambdas.
Rainer Joswig

Câu trả lời:


5

Lưu ý rằng, ngay cả sau khi defunvấn đề toàn cầu được giải quyết, bạn cần ít máy móc hơn bạn phải làm một cái gì đó như thế này. Ví dụ:

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

Sau đó

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

Rõ ràng account-operationchỉ là một tiện lợi.


5

Có lẽ bạn muốn định hướng đối tượng?

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

Sử dụng:

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

Chúng tôi có thể tạo tài khoản với số dư khác:

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

Để biết thêm, tôi đề xuất Sách dạy nấu ăn: https://lispcookbook.github.io/cl-cookbook/clos.html


4

Một quy tắc chung là chỉ defunnên được sử dụng khi xác định hàm ở cấp cao nhất. Để xác định các hàm cục bộ, hai toán tử đặc biệt fletlabelscó thể được sử dụng ( thủ công ).

Ví dụ:

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

labelslà như thế flet, nhưng nó được sử dụng khi có các định nghĩa đệ quy.

Sau đó, bạn không cần trả về các hàm bên trong hàm được trả về make-acc, nhưng trong đó bạn có thể thực hiện thao tác cần thiết:

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

Cuộc gọi sẽ đơn giản hơn và sẽ trả về giá trị mong đợi:

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

Cuối cùng, nếu bạn muốn, bạn cũng có thể trả lại hai chức năng khác nhau để thực hiện gửi tiền và rút tiền trên tài khoản:

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

sử dụng ví dụ này như:

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

3

Vấn đề nghiêm trọng duy nhất ở đây là defuntrong lisp chung không được sử dụng để xác định các hàm cục bộ.

Ví dụ, bạn có thể sử dụng lambdas cho các hoạt động đó, đặc biệt là khi bạn muốn trả lại 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)))))

Lưu ý rằng tôi đã sử dụng let*thay letvì bởi vì bạn cần có thể nhìn thấy balancetrong hai ràng buộc sau.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.