From 4ae2855478af22bc4fde3cbd945b5e38e6c9c00b Mon Sep 17 00:00:00 2001 From: Jan Moringen Date: Tue, 4 Mar 2014 03:32:43 +0100 Subject: [PATCH] minor corrections --- els-specializers.org | 172 ++++++++++++++++++++++++------------------- 1 file changed, 97 insertions(+), 75 deletions(-) diff --git a/els-specializers.org b/els-specializers.org index a5534c4..3d426e1 100644 --- a/els-specializers.org +++ b/els-specializers.org @@ -53,7 +53,7 @@ efficient. #+begin_LaTeX \category{D.1}{Software}{Programming Techniques}[Object-oriented Programming] -\category{D.3.3}{Programming Languages}{Language Constructs and Features} +\category{D.3.3}{Programming Languages}{Language Constructs and Features} \terms{Languages, Design} \keywords{generic functions, specialization-oriented programming, method selection, method combination} #+end_LaTeX @@ -89,7 +89,10 @@ efficient. the object system behaviour, those possibilities cannot extend arbitrarily in all directions. There is still an expectation that functionality is implemented with methods on generic functions, - acting on objects with slots. Nevertheless, the MOP is flexible, + acting on objects with slots. + Example of something not doable: handling "message not understood" + message from message passing paradigm. + Nevertheless, the MOP is flexible, and is used for a number of things, including: documentation generation (where introspective functionality in the MOP is used to extract information from a running system); object-relational @@ -103,6 +106,7 @@ efficient. around that point, and I'm sure I've seen it somewhere but I can't remember where. Alternatively, there's Kiczales et al "MOPs: why we want them and what else they can do", fig. 2 ] + [AMOP, page 5] paints that picture, but again, only using words :) One area of functionality where there is scope for customization by the metaprogrammer is in the mechanics and semantics of method @@ -173,28 +177,31 @@ efficient. :PROPERTIES: :CUSTOM_ID: Cons :END: - We start by presenting our original use case, performing - dispatching on the first element of lists. Semantically, we allow - the programmer to specialize any argument of methods with a new - kind of specializer, =cons-specializer=, which is applicable if and - only if the corresponding object is a =cons= whose =car= is =eql= - to the symbol associated with the =cons-specializer=; these - specializers are more specific than the =cons= class, but less - specific than an =eql-specializer= on any given =cons=. - - One motivation for the use of this specializer is in an extensible - code walker: a new special form can be handled simply by writing an - additional method on the walking generic function, seamlessly - interoperating with all existing methods. - + One motivation for the use of generalized dispatch is in an + extensible code walker: a new special form can be handled simply by + writing an additional method on the walking generic function, + seamlessly interoperating with all existing methods. In this + use-case, dispatch is performed on the first element of lists. + Semantically, we allow the programmer to specialize any argument of + methods with a new kind of specializer, =cons-specializer=, which + is applicable if and only if the corresponding object is a =cons= + whose =car= is =eql= to the symbol associated with the + =cons-specializer=; these specializers are more specific than the + =cons= class, but less specific than an =eql-specializer= on any + given =cons=. + The programmer code using these specializers is unchanged from \cite{Newton.Rhodes:2008}; the benefits of the protocol described - here are centered on performance and generality: in an application - such as walking source code, we would expect to encounter special - forms (distinguished by particular atoms in the =car= position) - multiple times, and hence to dispatch to the same effective method - repeatedly. We discuss this in more detail in section [[#Memoization]]; - we present the metaprogrammer code below. + here are centered on performance and generality: + + alternative suggestions: modularity/improved code reuse/separation + of concerns since e.g. arbitrary method combinations can be used + + in an application such as walking source code, we would expect to + encounter special forms (distinguished by particular atoms in the + =car= position) multiple times, and hence to dispatch to the same + effective method repeatedly. We discuss this in more detail in + section [[#Memoization]]; we present the metaprogrammer code below. #+begin_src lisp (defclass cons-specializer (specializer) @@ -224,20 +231,25 @@ efficient. (and (consp o) (eql (car o) (%car s)))) #+end_src -The code above shows the core of the use of our protocol. We have +The code above shows a minimal use of our protocol. We have elided some support code for parsing and unparsing specializers, and for handling introspective functions such as finding generic functions for a given specializer. We have also elided methods on the protocol -function =specializer<=; for =cons-specializers= here, specializer +function =specializer<= + +mention =same-specializer-p=? especially when "only one +=cons-specializer=" touches on specializer equality. + +; for =cons-specializers= here, specializer ordering is trivial, as only one =cons-specializer= can ever be applicable to any given argument. See section [[#Accept]] for a case -where specializer ordering is substantially different. +where specializer ordering is non-trivial. -As in \cite{Newton.Rhodes:2008}, we can use these specializers to -implement a modular code walker, where we define one method per -special operator. We show two of those methods below, in the context -of a walker which checks for unused bindings and uses of unbound -variables. +As in \cite{Newton.Rhodes:2008}, the programmer can use these +specializers to implement a modular code walker, where they define one +method per special operator. We show two of those methods below, in +the context of a walker which checks for unused bindings and uses of +unbound variables. #+begin_src (defgeneric walk (form env stack) @@ -267,7 +279,11 @@ variables. for mediating dispatch contains the same information as the object representing the equivalence class of objects to which that specializer is applicable: here it is the =car= of the =cons= - (which we wrap in a distinct object); in the standard dispatch it + (which we wrap in a distinct object); + + the previous sentence is rather long and hard to understand + + in the standard dispatch it is the =class= of the object. This feature also characterizes those use cases where the metaprogrammer could straightforwardly use filtered dispatch \cite{Costanza.etal:2008} to implement their @@ -288,46 +304,52 @@ variables. of the specializer implementation very similar to the =cons= specializers in the previous section. + The metaprogrammer may choose to? We have chosen to compare signum values using \texttt{=}, which means that a method with specializer =(signum 1)= will be applicable to positive floating-point arguments (see the first method on =specializer-accepts-generalizer-p= and the method on - =specializer=accepts-p= below). This leads to one subtle + =specializer-accepts-p= below). This leads to one subtle difference in behaviour compared to that of the =cons= specializers: in the case of =signum= specializers, the /next/ method after any =signum= specializer can be different, depending on the class of the argument. This aspect of the dispatch is handled by the second method on =specializer-accepts-generalizer-p= below. + #+begin_src lisp (defclass signum-specializer (specializer) ((%signum :reader %signum :initarg :signum))) (defclass signum-generalizer (generalizer) ((%signum :reader %signum :initarg :signum))) +;; Not sure whether I am overlooking something but shouldn't this work: (defmethod generalizer-of-using-class - ((gf signum-generic-function) arg) - (typecase arg - (real (make-instance 'signum-generalizer - :signum (signum arg))) - (t (call-next-method)))) + ((gf signum-generic-function) (arg real)) + (make-instance 'signum-generalizer + :signum (signum arg))) (defmethod generalizer-equal-hash-key ((gf signum-generic-function) - (g signum-specializer)) + (g signum-generalizer)) (%signum g)) (defmethod specializer-accepts-generalizer-p ((gf signum-generic-function) (s signum-specializer) (g signum-generalizer)) + ;; Would EQL work here? I'm thinking there might be an invariant of the form + ;; (specializer-accepts-p gf o) + ;; <=> + ;; (specializer-accepts-generalizer-p gf s (generalizer-of-using-class gf o)) + ;; Or if that wrong, maybe we should explain why. (if (= (%signum s) (%signum g)) ; or EQL? (values t t) (values nil t))) (defmethod specializer-accepts-generalizer-p ((gf signum-generic-function) - (specializer sb-mop:specializer) - (thing signum-specializer)) + (s sb-mop:specializer) + (g signum-generalizer)) (specializer-accepts-generalizer-p - gf specializer (class-of (%signum thing)))) + gf s (class-of (%signum g)))) (defmethod specializer-accepts-p ((s signum-specializer) o) @@ -335,8 +357,8 @@ variables. #+end_src Given these definitions, and once again some more straightforward - ones elided for reasons of space, we can implement the factorial - function as follows: + ones elided for reasons of space, the programmer can implement the + factorial function as follows: #+begin_src lisp (defgeneric fact (n) @@ -369,12 +391,11 @@ variables. For example, a graphical web browser might by default send an =Accept= header such as =text/html,application/xml;q=0.9,*/*;q=0.8=. This should be - interpreted by a web server as meaning that if for a given resource - the server can provide content of type =text/html= (i.e. HTML), - then it should do so. Otherwise, if it can provide - =application/xml= content (i.e. XML of any schema), then that - should be provided; failing that, any other content type is - acceptable. + interpreted as meaning that if for a given resource the server can + provide content of type =text/html= (i.e. HTML), then it should do + so. Otherwise, if it can provide =application/xml= content + (i.e. XML of any schema), then that should be provided; failing + that, any other content type is acceptable. In the case where there are static files on the filesystem, and the web server must merely select between them, there is not much more @@ -388,7 +409,7 @@ variables. generic function must then perform method selection against the request's =Accept= header to compute the appropriate response. - The =accept-specializer= below implements the dispatch. It depends + The =accept-specializer= below implements this dispatch. It depends 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 @@ -411,23 +432,23 @@ variables. (defmethod specializer-accepts-generalizer-p ((gf accept-generic-function) (s accept-specializer) - (generalizer accept-generalizer)) - (values (q (media-type s) (tree generalizer)) t)) + (g accept-generalizer)) + (values (q (media-type s) (tree g)) t)) (defmethod specializer-accepts-generalizer-p ((gf accept-generic-function) - (s specializer) - (generalizer accept-generalizer)) + (s specializer) ; other listing has sb-mop:specializer + (g accept-generalizer)) (specializer-accepts-generalizer-p - gf s (next generalizer))) + gf s (next g))) (defmethod specializer< ((gf accept-generic-function) (s1 accept-specializer) (s2 accept-specializer) - (generalizer accept-generalizer)) + (g accept-generalizer)) (let ((m1 (media-type s1)) (m2 (media-type s2)) - (tree (tree generalizer))) + (tree (tree g))) (cond ((string= m1 m2) '=) (t (let ((q1 (q m1 tree))) @@ -438,15 +459,15 @@ variables. (t '<)))))) #+end_src - The metaprogrammer can then support dispatching in this way for - suitable objects, such as the =request= object representing a - client request in the Hunchentoot web server. The code below - implements this, by defining the computation of a suitable - =generalizer= object for a given request, and specifying how to - compute whether the specializer accepts the given request object - (=q= returns a number between 0 and 1 if any pattern in the =tree= - matches the media type, and =nil= if the media type cannot be - matched at all). + The metaprogrammer can then add support for objects representing + client requesting, such as instances of the =request= class in the + Hunchentoot web server, by translating these into + =accept-generalizer= instances. The code below implements this, by + defining the computation of a =generalizer= object for a given + request, and specifying how to compute whether the specializer + accepts the given request object (=q= returns a number between 0 + and 1 if any pattern in the =tree= matches the media type, and + =nil= if the media type cannot be matched at all). #+begin_src (defmethod generalizer-of-using-class @@ -456,14 +477,17 @@ variables. :header (tbnl:header-in :accept arg) :next (class-of arg))) (defmethod specializer-accepts-p - ((specializer accept-specializer) - (obj tbnl:request)) - (let* ((accept (tbnl:header-in :accept obj)) + ((s accept-specializer) + (o tbnl:request)) + (let* ((accept (tbnl:header-in :accept o)) (tree (parse-accept-string accept)) - (q (q (media-type specializer) tree))) + (q (q (media-type s) tree))) (and q (> q 0)))) #+end_src + In the =next= slot in the previous listing and this listing is not + discussed, I think. + This dispatch cannot be implemented using filtered dispatch, except by generating anonymous classes with all the right mime-types as direct superclasses in dispatch order; the filter would generate @@ -471,7 +495,7 @@ variables. (ensure-class nil :direct-superclasses '(text/html image/webp ...)) #+end_src - and dispatch the operates using those anonymous classes. While + and dispatch 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 @@ -504,8 +528,8 @@ variables. :header s :next (class-of s))) (defmethod specializer-accepts-p - ((s accept-specializer) (string string)) - (let* ((tree (parse-accept-string string)) + ((s accept-specializer) (o string)) + (let* ((tree (parse-accept-string o)) (q (q (media-type s) tree))) (and q (> q 0)))) #+end_src @@ -922,5 +946,3 @@ variables. [fn:4] https://github.com/m2ym/optima [fn:5] http://clojure.org/multimethods - - -- 2.30.2