I’ve had a long road to understanding Lisp and OOP. Closures are the key. Closures allow variables to be encapsulated.
(let ((counter 0)) (defun inc-counter () (incf counter)))
This code encapsulates a variable such that every time the function inc-counter is used it will increment the value stored in the variable counter. The function inc-counter would be considered global.
If we want to create individual instances of the counter incrementer we could then write something like this:
(defun make-counter () (let ((counter 0)) (lambda () (incf counter))))
Now all we have to use the increment count is assign the object then make a funcall on it.
(setf count1 (make-counter)) (setf count2 (make-counter)) (funcall count1) (funcall count1) (funcall count1) (funcall count2)
Count1’s counter variable will contain 3 and count2’s counter variable will have a value of 1.
Wohoo! Big deal you say. Well it is a big deal becuase now we can extend this concept to creating a more robust object. Right now this object only gives us state information when we incrementer the counter. This won’t do any good if we use it and then store it in a local variable everytime we invoke an increment. Why have encapsulation? We need to have some way to get access to state information without actually incrementing the counter.
Looking at make-counter we see that we returned a lambda expression. Let us expand on this idea of utilizing a lambda expression for incrementing a counter and also returning it’s state ref .
(defun make-counter1 () (let ((counter 0)) (lambda (cmd) (case cmd (:increment (incf counter)) (:state counter)))))
A single lambda expression that takes a command as a parameter to access which function we want to execute.
(defparameter count3 (make-counter1)) (funcall count3 :increment) (funcall count3 :increment) (funcall count3 :increment) (funcall count3 :state)
Now there is another way to do this (where in lisp is there not another way to do this?) ref.
(defun make-counter2 () (let ((counter 0)) (list (lambda() (incf counter)) (lambda () counter))))
A list of lambda expressions that we will need to access individual lambdas.
(defparameter count4 (make-counter2)) (funcall (first count4)) (funcall (first count4)) (funcall (first count4)) (funcall (second count4))
The source for code and data is the same. Code is comprised of s-expressions and data is comprised of s-expressions. This allows the lambdas to be stored in a list.
There is a third way to this. This will use a property list to get access to the lambdas.
(defun make-counter3 () (let ((counter 0)) (list :increment (lambda() (incf counter)) :state (lambda () counter)))) (defparameter count5 (make-counter3)) (funcall (getf count5 :increment)) (funcall (getf count5 :increment)) (funcall (getf count5 :increment)) (funcall (getf count5 :state))
So we have discussed several ways to implement an object. All four objects implement a mechanism to execute on closures. The underlying structure in which we implement the counter object would be a matter of taste!
The only problem with this type of object creation is it will be implementation specific , but also a lot of redundancy in coding.
In Part 2, we will get into this idea of using macros to create our own objects with minimal coding.
syntax highlighted by Code2HTML, v. 0.9.1