From: Joel Ray Holveck
Subject: Instantiable generic functions
Date: 
Message-ID: <1163734239.891377.129470@j44g2000cwa.googlegroups.com>
I'm trying to write generic functions that are also classes, and could
use a MOP guru's advice.

I want able to have objects that are funcallable, but also
instantiable.  Preferably, I'd like them to have all the behavior of
standard-generic-function and standard-class.

When I say "instantiable", I mean that you can call make-instance on
the class object, and get back an instance object that knows what it
is... that is, the class object can be a specializer for other methods.

One possible application for this (a simplification of my intended
application for rhetorical purposes) is to have invocations of these
GFs to be able to record their invocations, for methods to specialize
on these invocation records.  It would be good for these instances to
also be instances of other classes that are instances of
standard-object; that is, the metaclass that I use for this should be
compatible with standard-class.

(Note that specifying a superclass of an instantiable GF doesn't affect
what the GF does when it's called... it only affects the instances.)

So here's what I'm wanting to do, with do-something being an example of
one of these objects:

;; This is a random class I'm using to demonstrate compatibility with
;; standard-class.
(defclass some-random-class ())
(defmethod twiddle ((obj some-random-class))
  (print 'twiddled))

;; First, I define it in some way.  I'm using defclass for rhetorical
;; purposes; defgeneric would also make as much sense, but in
;; my code I'll be using a custom macro, so I can use
;; whatever mechanisms are needed (directly calling
;; make-instance, etc).

(defclass do-something (instantiable-gf-top-level-object
some-random-class)
  ((some-value :initarg :some-value))
  (:metaclass instantiable-gf-metaclass))
(setf (symbol-function 'do-something) (find-class 'do-something))

;; I can assign methods to DO-SOMETHING, and call it.  (This doesn't
;; necessarily make an instance.)
(defmethod do-something (x y)
  (print 'did-something))
(defmethod do-something ((x foo) y)
  (print '(did-something with a foo)))
(do-something 1 2)

;; I can instantiate DO-SOMETHING.  (This doesn't necessarily call
;; DO-SOMETHING.)
(defvar *deed* (make-instance 'do-something :some-value 105))
(slot-value *deed* some-value) => 105
;; I can also specialize using DO-SOMETHING.
(defmethod frob ((x do-something))
  (print 'frobbed))
(frob *deed*)
;; Note that *deed* is also an instance of some-random-class, so the
;; twiddle method defined above is invoked here.
(twiddle *deed*)

For do-something to be funcallable, then instantiable-gf-metaclass
needs to be an instance of funcallable-standard-class.  Now, I'm
unclear of the requirements for instantiation, but it seems that for
do-something to be instantiable, then instantiable-gf-metaclass needs
to be a subclass of standard-class.  (I mean, I could define a
make-instance that returns, say, a vector to hold the slots, but you
couldn't specialize against do-something then.)

This is about as far as I've gotten before getting stuck.  Anybody have
any advice?  Am I trying to go beyond what is reasonable here?

Cheers,
joelh

From: Pascal Costanza
Subject: Re: Instantiable generic functions
Date: 
Message-ID: <4s8v1lFuek6pU1@mid.individual.net>
Joel Ray Holveck wrote:

> I want able to have objects that are funcallable, but also
> instantiable.  Preferably, I'd like them to have all the behavior of
> standard-generic-function and standard-class.

In principle, it would be possible to achieve this by combining the two 
classes into a single one:

(defclass instantiable-generic-function
   (standard-generic-function standard-class)
   ())

_However_: The problem here is (a) that the CLOS MOP doesn't allow this 
to be specified and (b) that this is because this would mix up 
base-level and meta-level concepts.

(a) is a formal restriction: All classes that are derived from the class 
'class need to be checked for compatibility when they are used together. 
This applies to standard-class and funcallable-standard-class which are 
derived from class. For example, assume you attempt to define the 
following class:

(defclass some-class ()
   ()
   (:metaclass instantiable-generic-function))

Due to the CLOS inheritance rules, this will (sooner or later) add 
standard-object as the default superclass. Standard-object is an 
instance of standard-class, so you would have to declare compatibility 
between standard-class and instantiable-generic-function through a 
method for validate-superclass. However, the CLOS MOP specifies that 
standard-generic-function and standard-class are _not_ compatible, and 
you cannot make them compatible after the fact.

(b) The deeper reason here is that the two classes standard-class and 
standard-generic-function are not at the same level. You have to 
distinguish three levels (at least): the base-level, the class level and 
the metaclass level. At the base level, you have plain objects which are 
instances of standard-object (most of the time via inheritance). 
Standard-object is an object at the class level. Standard-object itself 
is an instance of standard-class which is an object at the metaclass level.

The same for generic functions: A concrete generic function is an object 
at the base level. It is, in the general case, an instance of 
standard-generic-function which is derived from 
funcallable-standard-object and is an object at the class level. 
Standard-generic-function, in turn, is an instance of 
funcallable-standard-class which is an object at the metaclass level.

When you want to combine standard-generic-function and standard-class 
into one class, you actually attempt to combine something at the class 
level with something at the metaclass level. Mixing up levels is, in the 
general case, a bad idea. [1]


As a consequence, this means that you should only derive your 
instantiable-generic-function from one of the two classes and have to 
implement the functionality of the other from scratch yourself. I would 
suggest to start from standard-generic-function and reimplement 
standard-class functionality yourself because turning a "regular" object 
into a funcallable one isn't possible in a straightforward way.

Another option may be to create your instantiable functions as sibling 
objects, one an instance of standard-generic-function and the other an 
instance of standard-class, and then link them so that you can easily 
move from one to the other.

I hope this helps.


Pascal


[1] This is actually similar to using eval in regular Lisp code.

-- 
My website: http://p-cos.net
Common Lisp Document Repository: http://cdr.eurolisp.org
Closer to MOP & ContextL: http://common-lisp.net/project/closer/
From: Joel Ray Holveck
Subject: Re: Instantiable generic functions
Date: 
Message-ID: <1163916717.399772.229810@h54g2000cwb.googlegroups.com>
Pascal Costanza wrote:

>> I want able to have objects that are funcallable, but also
>> instantiable.  Preferably, I'd like them to have all the behavior of
>> standard-generic-function and standard-class.
> In principle, it would be possible to achieve this by combining the two
> classes into a single one:
> (defclass instantiable-generic-function
>    (standard-generic-function standard-class)
>    ())
> _However_: The problem here is (a) that the CLOS MOP doesn't allow this
> to be specified and (b) that this is because this would mix up
> base-level and meta-level concepts.

Thanks for the explanation!  I wasn't sure if (a) was something I could
get around by reimplementing a fair bit of the generic-function
functionality on my own, using funcallable-standard-object as a basis.
But all of my experiments came to naught.

However, (b) is, as you say, a deeper reason.  Since a basic principle
of the MOP is that classes are themselves instances, I thought that
perhaps the class-metaclass distinction was an illusion that could be
shattered with sufficient consideration.  Nevertheless, I certainly was
getting confused trying to keep the class-metaclass distinctions clear
in my head.  If, as you say, it's a bad general practice to try to blur
that boundary, then having that in mind now certainly will save me
headaches as I write the rest of my code!

> As a consequence, this means that you should only derive your
> instantiable-generic-function from one of the two classes and have to
> implement the functionality of the other from scratch yourself. I would
> suggest to start from standard-generic-function and reimplement
> standard-class functionality yourself because turning a "regular" object
> into a funcallable one isn't possible in a straightforward way.

That was one of the directions I experimented with.  The problem is, I
couldn't figure out how to get class-of (presumably needed for method
dispatch) to recognize the class of my replacement metaclass's
instances' instances.  By reimplementing standard-object, I lose
class-of's ability to recognize their classes.

> Another option may be to create your instantiable functions as sibling
> objects, one an instance of standard-generic-function and the other an
> instance of standard-class, and then link them so that you can easily
> move from one to the other.

This seems like it may be a more productive line of implementation.
The idea of having the invocation records being instances of the GFs is
more an idea of "it intuitively feels like the right thing", rather
than being a primary technical requirement.

> I hope this helps.

It does... again, thanks for the explanation!

Best,
joelh