We create a metaclass <zmodn> which is the class of the classes <Zmod3>, <Zmod5>, <Zmod7>, etc. (defclass <zmodn-class> (<class>) ((n initarg n reader zmodn-class-n)) metaclass <class>)
This will be a direct subclass of class, and so will inherit its methods, in particular the ability to create subclasses which are themselves classes. The instances of this class will have a slot named n, which will be the modular base.
Now we define a superclass for all of its instances, to place them in their own sub-hierarchy of the class graph. This class has an instance variable z, since the instances of its subclasses are the fully instantiated modular numbers. (defclass <zmodn-object> (<number>) ((z accessor zmodn-z)) metaclass <zmodn-class>) The metaclass of the instances of zmodn-object is defined to be the class zmodn-class. Thus the structure of the instances (the classes Zmod5, etc.) is determined by zmodn-class.
The constructor for the instances of zmodn-class (the metaclass) could be the following: (defun make-zmodn-class (n) (make <zmodn-class> 'direct-superclasses (list <zmodn-object>) 'name (make-symbol (format nil "<zmod- a>" n)) 'n n)) The make-instance requires values for the slots in zmodn-class, which include n (the slot we defined), and direct-superclasses, a slot inherited from class.
If you want to avoid creating duplicate zmodn classes with the same N, try this definition instead:
(defconstant *zmodn-table* (make <table> 'comparator = 'hash-function generic-hash))
(defun make-zmodn-class2 (n) (or (table-ref *zmodn-table* n) (let ((cl (make-zmodn-class n))) ((setter table-ref) *zmodn-table* n cl) cl)))
The function to create the modular objects themselves could be defined as follows: (defun make-modular-number (z n) (make-instance (make-zmodn-class2 n) 'z z)) Note that this implemenatation guarentees that the number is of the appropriate range: (defmethod initialize ((proto <zmodn-object>) lst) (let ((i (call-next-method))) ((setter zmodn-z) i (remainder (scan-args 'z lst required-argument) (zmodn-n i))) i))
Getting z from one of these instances is already defined by the reader on zmodn-object. Getting n involves going to the class. Making this available from instances means defining the following function: (defgeneric zmodn-n (obj))
(defmethod zmodn-n ((z <zmodn-object>)) (zmodn-class-n (class-of z)))
Next, we want to define some simple arithmetic on modular numbers, for example, addition. However, this only makes sense if we have the same modulus in both of the summands. (defun compatible-moduli (n m) (if (= (zmodn-n n) (zmodn-n m)) t (error "incompatible moduli" Internal-Error)))
We define a method for addition on <zmodn-object>: this will then be inherited by each instance, viz., the actual rings <zmod3>, <zmod5>, and so on. (defmethod binary-plus ((n1 <zmodn-object>) (n2 <zmodn-object>)) (when (compatible-moduli n1 n2) (make-modular-number (+ (zmodn-z i) (zmodn-z j)) (zmodn-n i))))
We can add a method to the print function to view numbers prettily (defmethod generic-prin ((n <zmodn-object>) s) (format s " a<mod a>" (zmodn-z n) (zmodn-n n)))
Finally, some examples of numbers (deflocal zero5 (make-modular-number 0 5)) (deflocal one5 (make-modular-number 1 5)) (deflocal two5 (make-modular-number 2 5)) (deflocal three5 (make-modular-number 3 5)) (deflocal four5 (make-modular-number 4 5)) (deflocal zero3 (make-modular-number 0 3)) (deflocal one3 (make-modular-number 1 3)) (deflocal two3 (make-modular-number 2 3))
Now if we try an addition:
> (+ two5 four5) < 1<mod 5>We didn't have to specify a plus method for each modular ring individually: the single definition on the superclass suffices.
Thanks to Harley Davis for help on this section.