Search code examples
functionmethodscommon-lispclosgeneric-function

Understanding Generic Functions in Common Lisp?


In this answer, the user gave a very clear example on how classes and methods work together.

I will reprint the example here:


(defclass human () ())
(defclass dog () ())

(defmethod greet ((thing human))
  (print "Hi human!"))

(defmethod greet ((thing dog))
  (print "Wolf-wolf dog!"))

(defparameter Anna (make-instance 'human))
(defparameter Rex (make-instance 'dog))

(greet Anna) ;; => "Hi human"
(greet Rex)  ;; => "Wolf-wolf dog!"

My question is, using the same example:

  1. What value would creating a generic functions add?
  2. Why are generic functions useful? Are they like instances in other OO languages that provide structure?

It seems that generic functions are created in the background implicitly (not 100% sure). I notice that when I play with this example, if I create a method that has a different param structure than the first instance of the method, I get a generic function error.


Solution

  • What value would creating a generic functions add?

    I like to declare the generic function explicitly because it is possible to add documentation and declarations that are relative to the generic function (optimize for speed/space/debug), and other details such as method combination (a.k.a. when you have multiple methods applicable for a given call, this defines which are executed and in which order). Here for example I can define talk as having a progn method combination (all methods are executed as-if encapsulated in a progn form):

    (defgeneric talk (subject)
      (:documentation "Say something to standard output")
      (:method-combination progn))
    

    This is a bit contrived example, but here we go:

    (defclass human () ())
    (defmethod talk progn ((a human))
      (print "hello"))
    
    (defclass wolf () ())
    (defmethod talk progn ((a wolf))
      (print "owooooo!"))
    
    (defclass werewolf (human wolf) ())
    

    Defining a class that inherits from both means that a call to talk for an instance of the class can execute two methods (sorted in a somewhat topological order called the Method Resolution Order). So with this method combination all the methods are executed:

    * (talk (make-instance 'werewolf))
    "hello" 
    "owooooo!"
    

    But, I would say that being able to document the generic function is by itself a good enough reason to declare it with defgeneric.

    Why are generic functions useful?

    If you define talk as a generic function you allow any class to participate in code that calls talk (e.g. a library), this is a way to allow extensions without having to close the set of possible values, unlike using something like typecase in a function where you can only list a predefined set of cases.

    For example the standard functions print-object is called at various times in your Lisp implementation (in the inspector, the REPL, the debugger), if you want you can implement a method for your custom types without having to hack the internals of your environment.

    Are they like instances in other OO languages that provide structure?

    Generic functions are, unlike in other OO languages, not tied to a single class or instance. They can be specialized1 on more than one argument, which means none of them "owns" the generic function.


    1. specialized is defined as follows:

    specialize v.t. (a generic function) to define a method for the generic function, or in other words, to refine the behavior of the generic function by giving it a specific meaning for a particular set of classes or arguments.

    The idea behind this is that methods can be more or less specific: a method of two arguments a and b that specializes both on (a number) and (b string) is more specific than another one that specializes only on (b vector), and methods are actually sorted from the most specific to the least specific ones (resp. from the least to the most) when combining them. You can even specialize a function on (a (eql 10)) to cover only the specific case of an argument a being eql to 10.