Differences between Dylan and CLOS - the MAKE generic function

Duncan Rose’s work from 10 years ago continues to guide my KLOS effort. One of the things he mentioned in this post is the following:

The syntax is probably 95% what you’d expect from reading the Dylan docs, assuming a cursory understanding of Dylan syntax and a similar understanding of CL syntax. A few of the macros are slightly different (for example, many of the CL macros use keywords for “literals” in macros (such as :PANE in frame-defining forms - the Dylan uses just PANE), and CLIM’s MAKE-PANE has returned since CL doesn’t implement dispatching the same way Dylan does (Dylan’s method is superior in my humble opinion). Also the Dylan “xxx-setter” methods are replaced with CL “(setf xxx)” methods).

I’ve documented how to deal with keywords already, and that work continues, but his point about MAKE was something that I wanted to know more about. I found a thread on comp.lang.lisp from 2006 this afternoon and it elaborates on his comment about MAKE-PANE.

In his first post, he wrote:

Using EQL specializers works, […] but is insufficient for my needs since eql specializers don’t form any kind of superclass / subclass hierarchy (i.e. I’d need an eql specialized MAKE-INSTANCE for each abstract class in the class hierarchy, which I’d rather avoid…).

but looking at the Deuce source code, I think there is a specialized MAKE for each of the abstract class:

define sealed inline method make
    (class == <deuce-pane>, #rest initargs, #key, #all-keys)
 => (pane :: <simple-deuce-pane>)
  apply(make, <simple-deuce-pane>, initargs)
end method make;

Either way, maybe I’ll understand more once I have running code. It also brings to mind that singleton specialization is a must-have in KLOS.


Reading some more, I think I found the feature that’s unique to Dylan (and probably not in Common Lisp).

Here’s a snippet from ‘gadget-mixins.dylan’:

//--- If you change this method, change the one in sheets/frame-managers
define method make
    (pane-class :: subclass(<gadget>),
     #rest pane-options,
     #key port, frame-manager: framem, #all-keys)
 => (pane :: <gadget>)
  dynamic-extent(pane-options);
  let framem = framem
               | *current-frame-manager*
               | port-default-frame-manager(port | default-port())
               | error("Can't find a frame manager to use with 'make-pane'");
  let (concrete-class, concrete-options)
    = apply(class-for-make-pane, framem, pane-class, pane-options);
  // If there's a mapping from the abstract pane class to a concrete pane
  // class, then use it.  Otherwise just try to create a class named by the
  // abstract pane class.
  if (concrete-class == pane-class)
    apply(next-method, pane-class,
          frame-manager: framem, pane-options)
  else
    //---*** Unfortunately, this recursive call to make will call
    //---*** 'class-for-make-pane' again.  How to speed this up?
    apply(make, concrete-class,
          frame-manager: framem,
          concrete-options | pane-options)
  end
end method make;

The key thing is the specialization on pane-class :: subclass(<gadget>). This subclass specialization is spelled out here in DEP 5

With that available, here’s the code for creating buttons:

define function button-selection-mode-class
    (selection-mode :: <selection-mode>) => (class :: <class>)
  select (selection-mode)
    #"none"     => <push-button>;
    #"single"   => <radio-button>;
    #"multiple" => <check-button>;
  end
end function button-selection-mode-class;

define sealed inline method make
    (class == <button>, #rest initargs,
     #key selection-mode :: <selection-mode> = #"none", #all-keys)
 => (button :: <button>)
  apply(make, button-selection-mode-class(selection-mode), initargs)
end method make;

// Inside of things like menus, we want to be able to get the value of
// a button.  When the button is inside some kind of a button box, its
// value is the value of the client box.
define method button-gadget-value (button :: <button>) => (value)
  select (gadget-selection-mode(button))
    #"none" =>
      gadget-value(button);
    #"single", #"multiple" =>
      let client = gadget-client(button);
      when (client)
        gadget-value(client)
      end
  end
end method button-gadget-value;

This code is pretty neat - it does what Duncan Rose talks about here, which is how <button> returns a more specific implementation, and working with the Frame Manager, it’ll also return an implementation-specific class (<gtk-button> or <win32-button>, for example).