Christophe Weblog Wiki Code Publications Music
add one more "interesting" signum method
[paper-els-specializers.git] / els-specializers.org
1 #+TITLE: Generalizers: New Metaobjects for Generalized Dispatch
2 #+AUTHOR: Christophe Rhodes, Jan Moringen, David Lichteblau
3 #+OPTIONS: toc:nil
4
5 #+LaTeX_HEADER: \usepackage[margin=1in]{geometry}
6
7 #+begin_abstract
8 1. This paper introduces a new metaobject, the generalizer, which
9    complements the existing specializer metaobject.
10 2. With the help of examples, we show that this metaobject allows for
11    the efficient implementation of complex non-class-based dispatch
12    within the framework of existing metaobject protocols
13 3. We present the generalizer protocol, implemented within the SBCL
14    implementation of Common Lisp
15 4. In combination with previous work, this produces a fully-functional
16    extension of the existing mechanism for method selection and
17    effective method computation, including support for standard and
18    user-defined method combination independent from method selection.
19 #+end_abstract
20
21 * Introduction
22   The revisions to the original Common Lisp language \cite{CLtL1}
23   included the detailed specification of an object system, known as
24   the Common Lisp Object System (CLOS), which was eventually
25   standardized as part of the ANSI Common Lisp standard \cite{CLtS}.
26   The object system as presented to the standardization committee was
27   formed of three parts, the first two of which covered XXX [what?]
28   and were incorporated into the final standard, and the third,
29   covering a Metaobject Protocol (MOP) for CLOS, was not.
30
31   Nevertheless, the CLOS MOP has proven to be a robust design, and
32   while many implementations have derived their implementations of
33   CLOS from either the Closette illustrative implementation in
34   \cite{AMOP}, or the Portable Common Loops implementation of CLOS
35   from Xerox Parc, there have been from-scratch reimplementations of
36   CLOS (in at least CLISP; check for others -- ABCL?  Lisp500?!)
37   incorporating the majority of the Metaobject Protocol as described.
38
39   Although it has stood the test of time, the MOP is neither without issues
40   (e.g. M-M-L considered harmful; slot-definition initargs issue) nor
41   a complete framework for the metaprogrammer to implement all
42   conceivable variations of object-oriented behaviour; indeed, while
43   metaprogramming offers some possibilities for customization of the
44   object system behaviour, those possibilities cannot extend
45   arbitrarily in all directions.   There is still an expectation that
46   functionality is implemented with methods on generic functions,
47   acting on objects with slots.  [XXX find Paepke picture here?  Not
48   Paepke; AMOP?].  XXX include typical examples of MOP: object
49   persistence; maybe ref. Kizcales "MOPs: why we want them and what
50   else they can do"? (Fig. 2 in that is good) ORMs; sparse slots.
51   jmoringe:
52   + introspection, e.g. documentation generation
53   + programmatic construction of classes and generic functions
54     e.g. for IDL compilers, model transformations
55
56   One area of functionality where there is scope for customization by
57   the metaprogrammer is in the mechanics and semantics of method
58   applicability and dispatch.  While in principle AMOP allows
59   customization of dispatch in various different ways (the
60   metaprogrammer can define methods on protocol functions such as
61   =compute-applicable-methods=,
62   =compute-applicable-methods-using-classes=), for example, in
63   practice implementation support for this was weak until relatively
64   recently (ref. closer, also check how ContextL and filtered dispatch
65   are implemented).
66   jmoringe: filtered dispatch uses a custom method combination, i
67   think
68
69   Another potential mechanism for customizing dispatch is implicit in
70   the class structure defined by AMOP: standard specializer objects
71   (instances of =class= and =eql-specializer=) are generalized
72   instances of the =specializer= protocol class, and in principle
73   there are no restrictions on the metaprogrammer constructing
74   additional subclasses.  Previous work [Newton/Rhodes] has explored
75   the potential for customizing generic function dispatch using
76   extended specializers, but as of that work the metaprogrammer must
77   override the entirety of the generic function invocation protocol
78   (from =compute-discriminating-function= on down), leading to toy
79   implementations and duplicated effort.
80
81   This paper introduces a protocol for efficient and controlled
82   handling of arbitrary subclasses of =specializer=.  In particular,
83   it introduces the =generalizer= protocol class, which generalizes
84   (ahem) the return value of =class-of=, and allows the metaprogrammer
85   to hook into cacheing schemes to avoid needless recomputation of
86   effective methods for sufficiently similar generic function
87   arguments (See Figure\nbsp\ref{fig:dispatch}).
88
89   #+CAPTION:    Dispatch Comparison
90   #+LABEL:      fig:dispatch
91   #+ATTR_LATEX: width=0.9\linewidth float
92   [[file:figures/dispatch-comparison.pdf]]
93
94   The remaining sections in this paper can be read in any order.  We
95   give some motivating examples in section XX, including
96   reimplementations of examples from previous work, as well as
97   examples which are poorly supported by previous protocols.  We
98   describe the protocol itself in section YY, describing each protocol
99   function in detail and, where applicable, relating it to existing
100   protocol functions within the CLOS MOP.  We survey related work in
101   more detail in section ZZ, touching on work on customized dispatch
102   schemes in other environments.  Finally, we draw our conclusions
103   from this work, and indicate directions for further development, in
104   section WW; reading that section before the others indicates
105   substantial trust in the authors' work.
106 * Examples
107   - [ ] =cons-specializer= (can be done using filtered dispatch)
108   - [ ] factorial (like filtered dispatch)
109   - [ ] HTTP Accept header
110   - [ ] xpattern
111   - [ ] prototype/multimethod
112 ** car-of-cons
113    NB this example can be done using filtered dispatch, with a filter
114    calling =car= on cons arguments.
115
116    Note also that there's no real need for =cons-specializer= and
117    =cons-generalizer= to be distinct classes (as with =class= and
118    =class=).  Also true for =signum=, below; but more interesting
119    dispatch reveals the need to split.
120 #+begin_src lisp
121 (defclass cons-specializer (specializer)
122   ((%car :reader %car :initarg :car)))
123 (defclass cons-generalizer (generalizer)
124   ((%car :reader %car :initarg :car)))
125 (defmethod generalizer-of-using-class ((gf cons-generic-function) arg)
126   (typecase arg
127     ((cons symbol) (make-instance 'cons-generalizer :car (car arg)))
128     (t (call-next-method))))
129 (defmethod generalizer-equal-hash-key ((gf cons-generic-function)
130                                        (g cons-generalizer))
131   (%car g))
132 (defmethod specializer-accepts-generalizer-p ((gf cons-generic-function)
133                                               (s cons-specializer)
134                                               (g cons-generalizer))
135   (if (eql (%car s) (%car g))
136       (values t t)
137       (values nil t)))
138 (defmethod specializer-accepts-p ((s cons-specializer) o)
139   (and (consp o) (eql (car o) (%car s))))
140
141 #| less interesting methods elided: jmoringe: (un)parsing, specializer<?, more? |#
142
143 #| XXX insert motivating example from Newton/Rhodes here |#
144 #+end_src
145 ** signum
146    NB this example can definitely be done using filtered dispatch.
147
148    Point out obvious similarity between this and car-of-cons.  Note
149    the subtlety, though, in generalizer-of-using-class / signum wrt
150    rational vs floating point arguments
151 #+begin_src lisp
152 (defclass signum-specializer (specializer)
153   ((%signum :reader %signum :initarg :signum)))
154 (defclass signum-generalizer (generalizer)
155   ((%signum :reader %signum :initarg :signum)))
156 (defmethod generalizer-of-using-class ((gf signum-generic-function) arg)
157   (typecase arg
158     (real (make-instance 'signum-generalizer :signum (signum arg)))
159     (t (call-next-method))))
160 (defmethod generalizer-equal-hash-key ((gf signum-generic-function)
161                                        (g signum-specializer))
162   (%signum g)) ; this will create multiple entries for the same emf, but that's OK
163 (defmethod specializer-accepts-generalizer-p ((gf signum-generic-function)
164                                               (s signum-specializer)
165                                               (g signum-generalizer))
166   (if (= (%signum s) (%signum g)) ; or EQL?
167       (values t t)
168       (values nil t)))
169
170 ;; this method is perhaps interesting enough to talk about?
171 (defmethod specializer-accepts-generalizer-p ((gf signum-generic-function) (specializer sb-mop:specializer) (thing signum-specializer))
172   (specializer-accepts-generalizer-p gf specializer (class-of (%signum thing))))
173
174
175 (defmethod specializer-accepts-p ((s signum-specializer) o)
176   (and (realp o) (= (%signum s) (signum o))))
177
178 #| again elide more boring methods |#
179
180 (defgeneric fact (n)
181   (:generic-function-class signum-generic-function))
182 (defmethod fact ((n (signum 0))) 1)
183 (defmethod fact ((n (signum 1))) (* n (fact (1- n))))
184 (defmethod fact ((n (signum -1)))
185   (error "factorial of negative number: ~D" n))
186 #+end_src
187 ** HTTP Accept header
188    implement RFC2616 content negotiation
189
190    NB this definitely can't be done with filtered dispatch, except by
191    generating anonymous classes with all the right mime-types as
192    direct superclasses in dispatch order,so the filter does
193 #+begin_src lisp
194 (ensure-class nil :direct-superclasses '(text/html image/webp ...))
195 #+end_src
196    and dispatch is defined by using those classes.  And that's even
197    more awkward than it sounds, because that means that in principle
198    all the mime types in the universe need an existence as classes, to
199    cater for arbitrary mime types in accept headers.  And handling
200    wildcards is pretty much impossible, too.  See that in
201    =specializer<= which involves a nontrivial ordering of methods
202    (whereas in two above previous cases only a single extended
203    specializer could be applicable to any given argument)
204
205    Also of interest: note that we can have these
206    specializer/generalizers handle arbitrary objects: the =string= and
207    =tbnl:request= methods are independent of each other; this
208    generalizes to dealing with multiple web server libraries.
209
210    jmoringe: the name =accept-specializer=, while sensible, may
211    confusing in this context because "accept" occurs as part of the
212    protocol with a different semantic.
213
214 #+begin_src lisp
215 (defclass accept-specializer (extended-specializer)
216   ((media-type :initarg :media-type :reader media-type)))
217 (defclass accept-generalizer ()
218   ((header :initarg :header :reader header)
219    (tree)
220    (next :initarg :next :reader next)))
221 (defmethod generalizer-equal-hash-key
222     ((gf accept-generic-function) (g accept-generalizer))
223    `(accept-generalizer ,(header g)))
224 (defmethod specializer-accepts-generalizer-p ((gf accept-generic-function) (s acc
225 ept-specializer) (generalizer accept-generalizer))
226   (values (q (media-type s) (tree generalizer)) t))
227 (defmethod specializer-accepts-generalizer-p ((gf accept-generic-function) (s sb-
228 mop:specializer) (generalizer accept-generalizer))
229   (specializer-accepts-generalizer-p gf s (next generalizer)))
230
231 (defmethod specializer< ((gf accept-generic-function) (s1 accept-specializer) (s2
232  accept-specializer) generalizer)
233   (cond
234     ((string= (media-type s1) (media-type s2)) '=)
235     (t (let ((q1 (q (media-type s1) (tree generalizer)))
236              (q2 (q (media-type s2) (tree generalizer))))
237          (cond
238            ((= q1 q2) '=)
239            ((< q1 q2) '>)
240            (t '<))))))
241
242 ;; here are the only methods that actually know about TBNL
243 (defmethod generalizer-of-using-class ((gf accept-generic-function) (arg tbnl:request))
244   (make-instance 'accept-generalizer
245                  :header (tbnl:header-in :accept arg)
246                  :next (class-of arg)))
247 (defmethod specializer-accepts-p ((specializer accept-specializer) (obj tbnl:requ
248 est))
249   (q (media-type specializer) (parse-accept-string (tbnl:header-in :accept obj)))
250 )
251
252 ;; we can define methods on STRING too, for debugging/simulation purposes
253 (defmethod generalizer-of-using-class ((gf accept-generic-function) (s string))
254   (make-instance 'accept-generalizer
255                  :header s
256                  :next (class-of s)))
257 (defmethod specializer-accepts-p ((s accept-specializer) (string string))
258   (q (media-type s) (parse-accept-string string)))
259 #+end_src
260
261    jmoringe: The role of =accept-generalizer.tree= and the =q=
262    function are hard to understand and may require some
263    explanation. However, the example with its distinct, asymmetric
264    specializers/generalizers, =accept-generalizer.next= and
265    =specializer<= is likely worth it.
266
267 ** Pattern / xpattern / regex / optima
268    Here's the /really/ interesting bit, but on the other hand we're
269    probably going to run out of space, and the full description of
270    these is going to take us into =make-method-lambda= territory.
271    A second paper?  Future work?
272 * Protocol
273 ** Generalizer
274    - [ ] =generalizer-of-using-class= (NB class of gf not class of object)
275    - [ ] =compute-applicable-methods-using-generalizers=
276    - [ ] =generalizer-equal-hash-key=
277    - [ ] =specializer-accepts-generalizer-p=
278    - [ ] =specializer-accepts-p=
279    - [ ] =specializer<=
280      jmoringe: If I remember correctly, closette has
281      =method-more-specific-p= should we aim for parity with that and
282      use =specializer-more-specific-p=? The downside would be that
283      =-p= indicates a Boolean return value which is not the case here.
284 ** Full protocol
285    Description and specification left for reasons of space (we'll see?)
286    - [ ] =same-specializer-p=
287    - [ ] =parse/unparse-specializer-using-class=
288    - [ ] =make-method-specializers-form=
289    - [ ] jmoringe: In an email, I suggested
290      =make-specializer-form-using-class=:
291
292      #+begin_quote
293      Could we change =make-method-specializers-form='s default
294      behaviour to call a new generic function
295      #+begin_src
296        make-specializer-form-using-class gf method name env
297      #+end_src
298      with builtin methods on =sb-mop:specializer=, =symbol=, =cons= (for
299      eql-specializers)? This would make it unnecessary to repeat
300      boilerplate along the lines of
301      #+begin_src lisp
302      (flet ((make-parse-form (name)
303               (if <name-is-interesting>
304                 <handle-interesting-specializer>
305                 <repeat-handling-of-standard-specializers>)))
306        `(list ,@(mapcar #'make-parse-form specializer-names)))
307      #+end_src
308      for each generic function class.
309      #+end_quote
310    - [ ] =make-method-lambda= revision (use environment arg?)
311
312      jmoringe: would only be relevant for pattern dispatch, right? I
313      think, we didn't finish the discussion regarding special
314      variables vs. environment vs. new protocol function
315 * Related Work
316   - [ ] Newton/Rhodes
317   - [ ] filtered dispatch -- the point is that our work continues to
318     be useful in cases where there are unbounded numbers of
319     equivalence classes but each given invokation involves a small
320     number of methods.
321   - [ ] ContextL / context-oriented programming -- dispatch occurs on
322     hidden layer argument being an instance of an anonymous class with
323     suitably arranged superclasses -- OK because set of layers is
324     bounded and under programmer control
325   - [ ] http://soft.vub.ac.be/Publications/2010/vub-tr-soft-10-04.pdf
326   - [ ] http://soft.vub.ac.be/lambic/files/lambic-ilc09.pdf
327   - [ ] http://soft.vub.ac.be/Publications/2011/vub-soft-phd-11-03.pdf
328   - [ ] Prototypes with Multiple Dispatch
329     http://sauerbraten.org/lee/ecoop.pdf -- extension of Self-style
330     object system to handle multiple equally-privileged "receivers".
331     A good test case for our protocol; handled adequately with
332     generalizer being the tuple of (roles,delegations), with some
333     thought needed for method redefinitions but otherwise working
334     fine.
335   - [ ] Sheeple
336 * Conclusions
337 ** Acknowledgments
338    We thank Lee Salzman, Pascal Costanza, Mikel Evins for their
339    helpful discussions