Christophe Weblog Wiki Code Publications Music
another train journey's worth of changes
authorChristophe Rhodes <csr21@cantab.net>
Sat, 22 Feb 2014 21:05:22 +0000 (21:05 +0000)
committerChristophe Rhodes <csr21@cantab.net>
Sat, 22 Feb 2014 21:05:22 +0000 (21:05 +0000)
els-specializers.org

index 59b1bd16fb0a6f2be7fccca6c9fdb8baf2941bb8..e2230e6b53244e33faa1364fd7365085ca9d289d 100644 (file)
   (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)
@@ -343,8 +356,9 @@ mop:specializer) (generalizer accept-generalizer))
            ((= 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)
@@ -357,27 +371,34 @@ est))
 
    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
@@ -385,7 +406,6 @@ est))
 (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.
@@ -438,7 +458,7 @@ est))
      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
@@ -459,6 +479,18 @@ est))
     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