What _is_ exciting about generic functions vs. message sending...

Pascal Costanza <[email protected]> Sat, 24 Feb 2007 17:37:40 +0100
Newsgroups gmane.comp.lang.lightweight
Message-ID <[email protected]>
Hi,

This whole discussion about naming issues has deteriorated. I am  
sorry about this because I have taken a part in that. Now, I'd like  
to make an attempt to get this discussion back on track.

Here are two or three examples what generic functions buy you:

1) Assume the standard OOP example of points and how to compare  
points. Here is an implementation in CLOS:

(defclass point ()
   ((x :initarg :x :reader x)
    (y :initarg :y :reader y)))

(defclass color-point (point)
   ((c :initarg :color :reader color)))

(defmethod eqv ((obj1 point) (obj2 point))
   (and (eql (x obj1) (x obj2))
        (eql (y obj1) (y obj2))))

(defmethod eqv ((obj1 color-point) (obj2 color-point))
   (and (call-next-method)
        (eql (color obj1) (color obj2))))

All the implementations in single-dispatch languages I have seen are  
ugly and amount to simulating multiple dispatch.


2) Here is an implementation of the Visitor pattern example given in  
Gamma et al.'s book on page 340ff.

;; A piece of equipment has a name and has a net price and discount  
price.

(defclass equipment ()
   ((name :initarg name :reader name)))

(defgeneric net-price (equipment))
(defgeneric discount-price (equipment))

;; For example, the net price of a CD drive is fixed and
;; the discount price of a CD drive is 90% of its net price.
;; Note how the reader for a slot turns into a method for
;; the generic function net-price.

(defclass cd-drive (equipment)
   ((price :initarg :net-price :reader net-price)))

(defmethod discount-price ((equipment cd-drive))
   (* (net-price equipment) 0.9))

;; A piece of equipment that consists of multiple parts.
;; We define net-price and discount-price via a
;; higher-order generic function.

(defclass composite-equipment (equipment)
   ((parts :initarg :parts :reader parts)))

(defmethod collect ((equipment composite-equipment) fun)
   (mapcar fun (parts equipment)))

(defmethod net-price ((equipment composite-equipment))
   (collect equipment #'net-price))

(defmethod discount-price ((equipment composite-equipment))
   (collect equipment #'discount-price))

;; For example, a laptop consists of a screen, a keyboard
;; and multiple internal parts.
;; Note how the method for collect suffices to
;; define net-price and discount-price accordingly.

(defclass laptop (composite-equipment)
   ((screen :initarg :screen :reader screen)
    (keyboard :initarg :keyboard :reader keyboard)))

(defmethod collect ((equipment laptop) fun)
   (+ (call-next-method)
      (funcall fun (screen equipment))
      (funcall fun (keyboard equipment))))

These are the base classes. Now let's take a look at how we define  
the visitors discussed in Gamma et al.'s book for these classes.

Here comes the kicker: We don't have to. The generic functions net- 
price and discount-price already do the job for us.

Here comes another kicker: None of the methods introduced in this  
specific example use multiple dispatch!!! [This is important for  
those of you who may wanted to object to the first example because it  
is seemingly about multiple dispatch, and not about generic functions.]


However, I would typically define methods for collect like this:

(defmethod collect ((equipment some-equipment) (fun function))
   ...)

This ensures that the method gets only called if fun is an instance  
of the class function. This is nice because it gives me an extra  
runtime type check for free. This is quite convenient in a  
dynamically typed language because I get an error signaled earlier if  
fun is not a function, which makes debugging easier.


So to summarize, the advantages of generic functions are as follows:

+ They make it easier to incorporate multiple dispatch. Since single  
dispatch is just a special case of multiple dispatch, you get all the  
advantages of single dispatch as well.

+ Since methods for generic functions belong to generic functions  
instead of classes, they have to be defined outside classes. This  
makes it easier to add new functionality to an existing class  
hierarchy without the need to resort to something like visitors.

+ Since in CLOS, methods can also be defined outside generic  
functions, you can still also add new classes together with "their"  
methods for existing generic functions. (This is a CLOS-specific  
advantage, not necessarily an ingredient of generic functions.)

+ Generic functions mesh a lot better with a higher-order functional  
programming style. You can pass generic functions around as  
parameters, and you can return generic functions from other  
functions. This is especially interesting for multiparadigm programming.


To me, generic functions bring a similar increase in expressiveness  
as compared to message sending, as message sending has brought as  
compared to imperative programming. To me conciseness of programs is  
more important than conciseness of names, especially names that only  
make sense in artificial toy examples.


Pascal


P.S.: I have already discussed the major disadvantages of generic  
functions a few days ago, so I won't repeat them here.

-- 
Pascal Costanza, mailto:[email protected], http://p-cos.net
Vrije Universiteit Brussel, Programming Technology Lab
Pleinlaan 2, B-1050 Brussel, Belgium