From the XLISP perspective, there are two kinds of bindings:
Global bindings are bindings to symbols in the *obarray*.
Lexical bindings are bindings in a local association list
There is a third kind of binding, 'dynamical binding', used by progv.
Have you ever wondered why this doesn't work:
(defun print-x () (print x)) ; unbound variable X (let ((x 'hello)) (print-x)) error: unbound variable - X
The answer is twofold:
The '
The '
Here is a version that seems to work:
(let ((x 'hello)) (defun print-x () (print x)) (print-x)) HELLO
The '
The '
But here again a version that does not behave as wanted:
(let ((x 'lexical)) (defun print-x () (print x))) (let ((x 'hello)) (print-x)) LEXICAL
The '
The '
Somehow it seems to be important where a function was defined.
Here a Lisp function, defined inside a let form:
(let ((a 'A) (b 'B) (c 'C)) (defun print-abc () (format t ";; a = ~s, b = ~s, c = ~s~%" a b c)) ) ; end of LET
Now '
> (print-abc) ;; a = A, b = B, c = C NIL
The lexical let variables 'a', 'b',
and 'c' have become a permanent part of the '
The following examples are based on
The function '
(defun new-account (name &optional (balance 0.0) (interest-rate 0.06)) #'(lambda (message) (case message (:withdraw #'(lambda (amount) (if (<= amount balance) (setq balance (- balance amount)) 'insufficient-funds))) (:deposit #'(lambda (amount) (setq balance (+ balance amount)))) (:balance #'(lambda () balance)) (:name #'(lambda () name)) (:interest #'(lambda () (setq balance (+ balance (* interest-rate balance))))))))
An account object can only do one thing, receive a message and return the appropriate function to execute that message. This function is called the 'method' that implements the message.
The function '
(defun get-method (object message) (funcall object message))
The function '
(defun send-message (object message &rest args) (apply (get-method object message) args))
Here are some examples how it works:
> (setq a1 (new-account "My Name" 1000.0)) #<Closure...> > (send-message a1 :name) "My Name" > (send-message a1 :balance) 1000.0 > (send-message a1 :withdraw 500.0) 500 > (send-message a1 :deposit 123.45) 623.45 > (send-message a1 :balance) 623.45
The '
For example if we want
(mapcar :balance accounts)
with '
(mapcar #'(lambda (acc) (send-message acc :balance)) accounts)
We could fix this problem by defining a generic function 'withdraw' like this:
(defun withdraw (object &rest args) (apply (get-method object :withdraw) args))
Now we can write:
(withdraw account x)
instead of:
(send-message account :withdraw x)
The macro '
(defmacro define-class (class ivars cvars &rest methods) `(let ,cvars (mapcar #'ensure-generic-function ',(mapcar #'first methods)) (defun ,class ,ivars #'(lambda (message) (case message ,@(mapcar #'make-clause methods))))))
The '
(defun make-clause (clause) `(,(car clause) #'(lambda ,(cadr clause) ,@(cddr clause))))
The '
(defun ensure-generic-function (message) (unless (generic-function-p message) (let ((fn #'(lambda (object &rest args) (apply (get-method object message) args)))) (setf (symbol-function message) fn) (putprop message fn 'generic-function))))
The '
(defun generic-function-p (name) (and (fboundp name) (eq (get name 'generic-function) (symbol-function name))))
Now we can define the 'account' class with '
(define-class account (name &optional (balance 0.0)) ((interest-rate 0.06)) (withdraw (amount) (if (<= amount balance) (setq balance (- balance amount)) 'insufficient-funds)) (deposit (amount) (setq balance (+ balance amount))) (balance () balance) (name () name) (interest () (setq balance (+ balance (* interest-rate balance)))))
Macroexpansion:
(let ((interest-rate 0.06)) (mapcar (function ensure-generic-function) (quote (withdraw deposit balance name interest))) (defun account (name &optional (balance 0)) (function (lambda (message) (case message (withdraw (function (lambda (amount) (if (<= amount balance) (setq balance (- balance amount)) (quote insufficient-funds))))) (deposit (function (lambda (amount) (setq balance (+ balance amount))))) (balance (function (lambda nil balance))) (name (function (lambda nil name))) (interest (function (lambda nil (setq balance (+ balance (* interest-rate balance)))))))))))
Here is how it works:
> (setq a2 (account "my-name" 2000.0) #<Closure...> > (balance a2) 2000 > (deposit a2 42.0) 2042 > (interest a2) 2164.52
Here is a '
(define-class password-account (password acc) () (change-password (pass new-pass) (if (equal pass password) (setq password new-pass) 'wrong-password)) (t (pass &rest args) (if (equal pass password) (if args (apply message (cons acc args)) (funcall message acc)) 'wrong-password)))
The definition of '
Here is how '
> (setq a3 (password-account "secret" a2)) #<Closure...> > (balance a3 "secret") 2164.52 > (withdraw a3 "guess" 2000.0) WRONG-PASSWORD > (withdraw a3 "secret" 2000.0) 164.52
Here is a '
(define-class limited-account (limit acc) () (withdraw (amount) (if (<= amount limit) (withdraw acc amount) 'over-limit)) (t (&rest args) (if args (apply message (cons acc args)) (funcall message acc))))
The 'withdraw' message is redefined to check if the account limit is exceeded, while the 't' clause passes all other messages unchanged:
> (setq a4 (password-account "pass" (limited-account 100.0 (account "limited" 500.0))) #<Closure...>