Notes for WVU cs310, Programming lanuages, Spring 2011.
Gold1 and 2:
Basic OO.
Gold3:
Intermediary OO.
Gold4:
This time, we mean it.
These pages describe all the details behing Graham's GOLD system. Those pages (chp 17 of Graham) should be read first.
A function named
x%
or
x%%
is a preliminary version of a function that will be refined,
later.
Anything starting with $
is a UNIX shell command; e.g.
$ ls abcd.lisp counts.lisp dist.lisp macros.lisp nb.lisp bounded.lisp data.lisp gold.lisp make.lisp random.lisp class.lisp deftest.lisp knn.lisp misc.lisp rbst.lisp
Anything starting with >
is a LISP REPL command; e.g.
> (+ 1 1) 2
(defun make () (handler-bind ((style-warning #'muffle-warning)) (load "gold.lisp")))
(defun rget% (prop obj) (multiple-value-bind (val in) (gethash prop obj) (if in (values val in) (let ((par (gethash :parent obj))) (and par ; if you have a parent (rget prop par)))))) ; go fetch!
To understand the above, you need to know that gethash
returns not
one, but two, values and the second of these values is a flag that is
non-nil if the key was not in the hash table (so now we can distinguish
between a missing slot and a slot that is present, but contains nil).
The form (multiple-value-bind values call progn)
catches the output
values
from call
and makes the available for the remaining
code in progn
.
The form (values val in)
is the way of defining multiple returning
values.
Finally, the form :parent
is a keyword
. Keywords evaluate
to themselves and are global to the entire LISP program (note quite-
they are global to the package inside of which they are defined).
(defun tell% (obj message &rest args) (apply (rget% message obj) obj args))
(defun !oo0 () "expect radius = 2 area = 12.566370614359172d0" (with-output-to-string (str) (!oo0-prim str)))
Aside: The String Trick. When writing tests, it is often useful to
catch the output from print statements. The above form
with-output-to-string defined a stream we can pass to
sub-routines. Those sub-routines can, optionally, use
that stream to write their output (see example below- note
that the next function can still run properly, even if it is
not within an with-output-to-string
form.
(defun !oo0-prim (&optional (str t)) (let (circle-class our-circle) (setf circle-class (make-hash-table) our-circle (make-hash-table) (gethash :parent our-circle) circle-class (gethash 'radius our-circle) 2 (gethash 'area circle-class) #'(lambda (x) (* pi (expt (rget 'radius x) 2 )))) (format str "~&radius = ~a area = ~a~%" (rget% 'radius our-circle) (tell% our-circle 'area))))
Note how, in the above, we set the parent of an object with :parent
.
Output:
"radius = 2 area = 12.566370614359172d0 "
As Graham says, before going on to improve this program, it's worth pausing to consider what we have done. With eight lines of code we have made plain old pre-CLOS Lisp into an object-oriented language. How did we manage to achieve such a feat? There must be some sort of trick involved, to implement object-oriented programming in eight lines of code.
There is a trick, but it is not a programming trick. The trick is, Lisp already was an object-oriented language, or rather, something more general. All we had to do was put a new facade on the abstractions that were already there.
Having said that, there are some issues with the above.
Everything hashed. not the fastest option
Ugly syntax. error prone. ideally, want someway to batch common operations
Has some OO features: association, polymorphishm, inheritance
Lacks others:
No concept of "class" seperaete to "instance" (anything can be something anything else can inherit from
No encapulation : variables referenced different to methods
Adding encapsulation:
(defun tell%% (obj message &rest args) (let ((what (rget message obj))) (if (functionp what) (apply (rget message obj) obj args) what)))
Advantage: now we can get everything via tell
.
Disadvantage: Setf is a problem. E.g. can't
> (setf (tell *timm* 'age) 22)
Support notes on upcoming code.
(Graham, p100)
By making the first argument to defun of function f
a list of the
form (setf x )
, you define what happens when the first argument to
setf is a call to f
.
The following pair of functions defines primo as a synonym for car:
(defun primo (1st) (car 1st)) (defun (setf primo) (val 1st) (setf (car 1st) val))
In the definition of a function whose name is of the form (setf x)
,
the first parameter represents the new value, and the remaining
parameters represent arguments to f
.
Now any setf of primo will be a call to the latter function above:
> (let ((x (list 'a 5b >c))) (setf (primo x) 480) x) (480 B C)
Mulitple inheritance means that if code exists in more than one parent, we must define a search order. When we want some code, we search in that order and return the first thing we find, in that order.
In the following example, we have a list of parents (initally first
and then country
).
(defun rget%%% (prop obj) (dolist (c (precedence% obj)) (multiple-value-bind (val in) (gethash prop c) (if in (return (values val in))))))
This is called precedence ordering.
It begins by calling traverse
to build a list representing the
objects encountered in a depth-first traversal:
If any of the objects share parents, there will be duplicates in this list.
If we preserve only the last of each set of duplicates, we will get a precedence list in the specialized oder.
The Common Lisp function delete-duplicates is defined to behave this way, so if we just call it on the result of the depth-first traversal, we will get the correct precedence list.
(defun precedence% (obj) (labels ((traverse (x) (cons x (mapcan #'traverse (gethash :parents x))))) (delete-duplicates (traverse obj))))
(defun !oo1 () (let (scoundrel patriot patriotic-scoundrel) (setf scoundrel (make-hash-table) patriot (make-hash-table) patriotic-scoundrel (make-hash-table) (gethash 'serves scoundrel) 'self (gethash 'serves patriot) 'country (gethash :parents patriotic-scoundrel) (list scoundrel patriot)) (rget%%% 'serves patriotic-scoundrel)))
For the next version of Gold, see Gold-v3.
Source code:
$ wget http://goo.gl/QuBNH
This doco:
$ wget http://goo.gl/OSB94
First load into LISP:
> (load "gold.lisp") ; ignore warning messages
Reloads:
> (make)
Tim Menzies (tim@menzies.us)
Share and enjoy.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.