Object Orientation and CLOS Lee Spector, lspector@hampshire.edu, 1995-1998 Object orientation: What is it? CLOS: classes & instances, generic functions & methods some of common lisp's types are also classes (describe 'number) Symbol: NUMBER Type Specifier, Class Name EXTERNAL in package: # Print name: "NUMBER" Value: # Function: # Plist: NIL Class: # --> NIL (describe 'symbol) Type Specifier, Class Name EXTERNAL in package: # Print name: "SYMBOL" Value: # Function: # Plist: NIL Class: # --> NIL Suppose I want to write a function that does different things to numbers and symbols (defun double (x) (typecase x (number (* 2 x)) (symbol (list x x)))) --> DOUBLE (double 33) --> 66 (double 'foo) --> (FOO FOO) The CLOS way to do this: first get rid of the function we just defined: (fmakunbound 'double) --> DOUBLE Now define it as a generic function: (defgeneric double (thing) (:documentation "Doubles its argument.")) --> # (defmethod double ((thing number)) "The doubling method for numbers." (* thing 2)) --> # (defmethod double ((thing symbol)) "The doubling method for symbols." (list thing thing)) --> # (double 33) --> 66 (double 'foo) --> (FOO FOO) The real power of CLOS comes from combining this "generic function dispatch" with user-definable classes. Defclass is like a super defstruct. (defstruct person (name 'bill) (age 10)) --> PERSON (make-person :name 'george :age 12) --> #S(PERSON :NAME GEORGE :AGE 12) This uses defclass for a similar effect: (defclass person () ((name :accessor name :initform 'bill :initarg :name) (age :accessor age :initform 10 :initarg :age))) --> # You have a lot more options with the defclass -- you have more flexibility with names, with what can be initialized, etc. You can also add doc strings for each slot. Instances of classes are made with make-instance: (make-instance 'person :age 100) --> # (defvar x) --> X (setq x (make-instance 'person :age 100)) --> # (describe x) Class: # Wrapper: # Instance slots NAME: BILL AGE: 100 --> NIL You can access and change slots with the "accessors" you specified in the defclass: (age x) --> 100 (setf (age x) 55) --> 55 (age x) --> 55 Objects are generally printed with PRINT-OBJECT, which is a generic function that we can specialize: x --> # (defmethod print-object ((p person) stream) (format stream "Person: ~A, age: ~A" (name p) (age p))) --> # x --> Person: BILL, age: 55 You can specialize classes -- child classes inherit structure and behavior from their parents: (defclass teacher (person) ((subject :accessor subject :initarg :subject))) --> # (defclass math-teacher (teacher) ((subject :initform "Mathematics"))) --> # (setq p2 (make-instance 'math-teacher :name 'john :age 34)) --> Person: JOHN, age: 34 We could provide a new definition of print-object for teachers, but CLOS lets us use and add to the behavior of the parent's definition: (defmethod print-object :after ((p teacher) stream) (format stream ", subject: ~A" (subject p))) --> # p2 --> Person: JOHN, age: 34, subject: Mathematics Here's a silly :before method for math teachers: (defmethod print-object :before ((p math-teacher) stream) (format stream "Axiomatically, I present: ")) --> # p2 --> Axiomatically, I present: Person: JOHN, age: 34, subject: Mathematics There are many ways in which multiple methods may be used in one generic function call -- see docs for details.