(and (consp o) (eql (car o) (%car s))))
#| less interesting methods elided: jmoringe: (un)parsing, specializer<?, more? |#
-
-#| XXX insert motivating example from Newton/Rhodes here |#
+#+end_src
+#+begin_src
+(defgeneric walk (form env vars)
+ (:generic-function-class cons-generic-function))
+(defmethod walk ((expr (cons lambda)) env call-stack)
+ (let ((lambda-list (cadr expr))
+ (body (cddr expr)))
+ (with-checked-bindings ((bindings-from-ll lambda-list) env call-stack)
+ (dolist (form body)
+ (walk form env (cons form call-stack))))))
+(defmethod walk ((expr (cons let)) env call-stack)
+ (with-checked-bindings ((mapcar (lambda (x) (walk (cadr x) env (cons (cadr x) call-stack)) (cons (car x) (make-instance 'binding))) (cadr expr)) env call-stack)
+ (dolist (form (cddr expr))
+ (walk form env (cons form call-stack)))))
#+end_src
- TODO Timings: generalizer-enabled vs not vs case (car x) for CL special
- operators. Tweak so that the overhead of =case= is actually important.
+ | implementation | time (ms / 100k calls) | overhead |
+ |-----------------------+------------------------+----------|
+ | cons-gf/no-cache | 9000 | +2700% |
+ | cons-gf | 1500 | +370% |
+ | cons-gf/one-arg-cache | 740 | +130% |
+ | gf/methods | 360 | +14% |
+ | function | 317 | |
Note that in this example there is no strict need for
=cons-specializer= and =cons-generalizer= to be distinct classes –
(progn (gc :full t) (time (dotimes (i 10000) (%fact 20))))
#+end_src
- | fact (signum-gf) | %fact (fun) | %%fact (gf / 1meth) | fact (signum-gf / 1arg hash-special-case) |
- |------------------+-------------+---------------------+-------------------------------------------|
- | 0.284 | 0.004 | 0.016 | 0.032 |
- | 0.076 | 0.008 | 0.012 | 0.024 |
- | 0.072 | 0.004 | 0.012 | 0.116 |
- | 0.264 | 0.004 | 0.008 | 0.120 |
- | 0.292 | 0.008 | 0.012 | 0.120 |
- | 0.264 | 0.004 | 0.016 | 0.084 |
- | 0.276 | 0.008 | 0.012 | 0.092 |
- | 0.264 | 0.008 | 0.012 | 0.036 |
- | 0.276 | 0.008 | 0.004 | 0.104 |
- | 0.272 | 0.004 | 0.012 | 0.020 |
+ | implementation | time (ms/10k calls) | overhead |
+ |-------------------------+---------------------+----------|
+ | signum-gf/no-cache | 2400 | +41000% |
+ | signum-gf | 230 | +3800% |
+ | signum-gf/one-arg-cache | 75 | +1100% |
+ | gf/fixnum | 12 | +100% |
+ | function | 6.0 | |
We could allow the metaprogrammer to improve on the one-argument
performance by constructing a specialized cache: for =signum=
before actually generating the best matching response.
The =accept-specializer= below implements the dispatch. It depends
- on a lazily-computed =tree= to represent the information in the
- accept header, and a function =q= to compute the (defaulted)
- preference level for a given content-type and =tree=; then, method
- selection and ordering involves finding the =q= for each
- =accept-specializer='s content type given the =tree=, and sorting
- them according to the preference level.
+ on a lazily-computed =tree= slot to represent the information in
+ the accept header (generated by =parse-accept-string=), and a
+ function =q= to compute the (defaulted) preference level for a
+ given content-type and =tree=; then, method selection and ordering
+ involves finding the =q= for each =accept-specializer='s content
+ type given the =tree=, and sorting them according to the preference
+ level.
#+begin_src lisp
(defclass accept-specializer (extended-specializer)
((= q1 q2) '=)
((< q1 q2) '>)
(t '<))))))
+#+end_src
-;; here are the only methods that actually know about TBNL
+#+begin_src
(defmethod generalizer-of-using-class ((gf accept-generic-function) (arg tbnl:request))
(make-instance 'accept-generalizer
:header (tbnl:header-in :accept arg)
This dispatch can't be done with filtered dispatch, except by
generating anonymous classes with all the right mime-types as
- direct superclasses in dispatch order,so the filter does
+ direct superclasses in dispatch order; the filter would generate
#+begin_src lisp
(ensure-class nil :direct-superclasses '(text/html image/webp ...))
#+end_src
- and dispatch is defined by using those classes. And that's even
- more awkward than it sounds, because that means that in principle
- all the mime types in the universe need an existence as classes, to
- cater for arbitrary mime types in accept headers. And handling
- wildcards is pretty much impossible, too. See that in
- =specializer<= which involves a nontrivial ordering of methods
- (whereas in two above previous cases only a single extended
- specializer could be applicable to any given argument)
-
- Also of interest: note that we can have these
- specializer/generalizers handle arbitrary objects: we can define
- methods on =string=, completely independently from the methods on
- =tbnl:request= methods are independent of each other; this
- generalizes to dealing with multiple web server libraries.
+ and dispatch the operates using those anonymous classes.
+
+ While this is possible to do, it is awkward to express content-type
+ negotiation in this way, as it means that the dispatcher must know
+ about the universe of mime-types that clients might declare that
+ they accept, rather than merely the set of mime-types that a
+ particular generic function is capable of serving; handling
+ wildcards in accept strings is particularly awkward in the
+ filtering paradigm.
+
+ Note that in this example, the method on =specializer<= involves a
+ nontrivial ordering of methods based on the =q= values specified in
+ the accept header (whereas in sections XX.1 and XX.2 only a single
+ extended specializer could be applicable to any given argument).
+
+ Also note that the accept specializer protocol is straightforwardly
+ extensible to other suitable objects; for example, one simple
+ debugging aid is to define that an =accept-specializer= should be
+ applicable to =string= objects. This can be done in a modular
+ fashion (see source example NN), and generalizes to dealing with
+ multiple web server libraries, so that content-negotiation methods
+ are applicable to each web server's request objects.
#+begin_src lisp
-;; we can define methods on STRING too, for debugging/simulation purposes
(defmethod generalizer-of-using-class ((gf accept-generic-function) (s string))
(make-instance 'accept-generalizer
:header s
(defmethod specializer-accepts-p ((s accept-specializer) (string string))
(q (media-type s) (parse-accept-string string)))
#+end_src
-
jmoringe: the name =accept-specializer=, while sensible, may
confusing in this context because "accept" occurs as part of the
protocol with a different semantic.
think, we didn't finish the discussion regarding special
variables vs. environment vs. new protocol function
* Related Work
- - [ ] Newton/Rhodes
+ - [ ] Newton/Rhodes, obv
- [ ] filtered dispatch -- the point is that our work continues to
be useful in cases where there are unbounded numbers of
equivalence classes but each given invokation involves a small
fine.
- [ ] Sheeple
* Conclusions
+ - protocol for straightforward definition of custom dispatch
+ + interoperates seamlessly with rest of CLOS: method combination,
+ etc.
+ + tolerably efficient: two extra standard gf invokations and one
+ hash table lookup per call on the fast path (but more to be
+ done)
+ + expressive: handles foms of dispatch not handled elsewhere; all
+ the usual niceties of redefinition, modularity, introspection
+** Future Work
+ - compute-cache-handling-functions (and general speed issues)
+ - automatic pattern-specializer generalizer computation
+ - prototype-oriented progamming a la Slate.
** Acknowledgments
We thank Lee Salzman, Pascal Costanza, Mikel Evins for their
helpful discussions