150 likes | 219 Views
Generics. "It is easy to study the rules of overloading and of templates without noticing that together they are one of the keys to elegant and efficient type-safe containers.“ -Bjarne Stroustrup. Polymorphism (1).
E N D
Generics "It is easy to study the rules of overloading and of templates without noticing that together they are one of the keys to elegant and efficient type-safe containers.“ -Bjarne Stroustrup
Polymorphism (1) • Because LISP doesn’t use type checking, we can easily write useful general functions that work for several different data types >(defun zip (xs ys) (if (and (consp xs) (consp ys)) (cons (cons (car xs) (car ys)) (zip (cdr xs) (cdr ys))) nil)) ZIP >(zip '(a b c d) '(1 2 3 4)) ((A . 1) (B . 2) (C . 3) (D . 4))
Polymorphism (2) • But what if we want a function to behave differently depending on the types of its arguments? (defstruct square (length 0)) (defstruct circle (radius 0)) (defun perimeter (shape) (cond ((square-p shape) (* 4 (square-length shape))) ((circle-p shape) (* 2 PI (circle-radius shape))) (t 0))) • We have to have a case for every possible shape • For any shape we add, we have to update the perimeter function
A Better Way: Generics • Generics allow the same function name to be associated with several different types (a kind of overloading) (defgeneric perimeter (shape)) (defmethod perimeter ((shape square)) (* 4 (square-length shape))) (defmethod perimeter ((shape circle)) (* 2 PI (circle-radius shape))) >(perimeter (make-square :length 5)) 20 >(perimeter (make-circle :radius 2)) 12.566370614359172954L0 • Generics also form the basis of LISP’s Object System (OOP) • Not supported in GCL yet. Try CLISP
Adding Methods • Now it is easy to expand the functionality of the perimeter method. • If we add a new shape… (defstruct rectangle (width 0) (height 0)) • We simply add a new method (defmethod perimeter ((shape rectangle)) (+ (* 2 (rectangle-width shape)) (* 2 (rectangle-height shape))) )
Extra Parameters • The type declarations following the variable name is called a specializer • Methods can take more than one parameter, optionally specializing on any of them (a method that doesn’t specialize on any parameter is basically a function) • type-of and typep are useful for determining type (be aware of supertypes) >(typep 5 'number) T > (type-of 5) (INTEGER 0 16777215)
Extra Parameters Example (defgeneric combine (a b)) (defmethod combine ((a number) (b number)) (+ a b)) (defmethod combine ((a character) (b character)) (code-char (+ (char-code a) (char-code b)))) (defmethod combine (a (b cons)) (cons a b)) >(combine 5 6) 11 >(combine #\A #\B) #\U0083 >(combine 'A '(5 B S 30)) (A 5 B S 30) >(combine 3 'A) Error
Benefits • Finally, some well structured type safety! • Methods throw an error if no definition fits the types • Extra expressive power: allows for polymorphism even when the underlying algorithms are different for different data types • Takes advantage of inheritance (as we’ll see in a moment. But first…)
While We’re Talking About Types • Structures can also require that their slots have particular types, providing even more type safety >(defstruct rectangle (width 0 :type number) (height 0 :type number)) RECTANGLE >(make-rectangle :width 5.6 :height 3) #S(RECTANGLE :WIDTH 5.6 :HEIGHT 3) >(make-rectangle :width 'A :height '(3 4 5)) Error • Now, on to dealing with inheritance…
Call-Next-Method • If we want a method to be mostly the same for all members of a class, but slightly specialized for certain subclasses, then we can split up the code across different methods • Done with call-next-method. It calls the next method that fits the arguments in decreasing order of specificity • Similar to calling methods of a super class in Java
Call-Next-Method Example (1) • Suppose we want to have several types of bank accounts • For all accounts, a withdraw consists of checking for available funds and removing them if possible • Savings accounts are only allowed 3 withdraws per month, or else there is an additional service charge of $3 for each withdraw • Checking accounts are sometimes linked to a savings account to provide overdraft protection. If a withdraw exceeds available funds in a checking account, then the difference is taken from the backup savings account • First design the structures (defstruct account (balance 0)) (defstruct (savings (:include account)) (withdraws 0)) (defstruct (checking (:include account)) (backup nil))
Call-Next-Method Example (2) • Now design the methods (defgeneric withdraw (source amount)) (defmethod withdraw ((source account) (amount number)) (let ((b (account-balance source))) (if (> amount b) (error "Insufficient funds") (progn (setf (account-balance source) (- b amount)) source)))) (defmethod withdraw ((source savings) (amount number)) (setf (savings-withdraws source) (1+ (savings-withdraws source))) (when (> (savings-withdraws source) 3) (setf amount (+ 3 amount)) ) (call-next-method source amount)) (defmethod withdraw ((source checking) (amount number)) (let ((overdraft (- amount (account-balance source)))) (when (and (plusp overdraft) (checking-backup source)) (withdraw (checking-backup source) overdraft) (setf (account-balance source) (+ (account-balance source) overdraft))) (call-next-method)))
Call-Next-Method Example (3) >(defvar s (make-savings :balance 1000 :withdraws 3)) S >(withdraw s 500) #S(SAVINGS :BALANCE 497 :WITHDRAWS 4) >(defvar c (make-checking :balance 10 :backup s)) C >(withdraw c 20) #S(CHECKING :BALANCE 0 :BACKUP #S(SAVINGS :BALANCE 484 :WITHDRAWS 5)) > (withdraw c 500) Insufficient funds error
Another Specializer • Methods can also be specialized based on equality with a specific value (defvar exception (make-savings :balance 1000 :WITHDRAWS 5)) (defmethod withdraw ((source (eql exception)) (amount number)) (setf (savings-withdraws source) 0) (call-next-method)) >(withdraw exception 200) #S(SAVINGS :BALANCE 800 :WITHDRAWS 1)
What Else Can Generics Do? • Although we’ve been using generics with structures, they were actually designed to work with the Common LISP Object System (CLOS) • LISP has types called classes which are defined with defclass • Classes aren’t too different from structures, though they are created (make-instance) and accessed (slot-value) differently • Perhaps the most interesting feature of classes is that they allow for multiple inheritance, which is not so scary in LISP since methods are defined separately from the classes • http://gigamonkeys.com/book/object-reorientation-classes.html can provide more information for those interested