home *** CD-ROM | disk | FTP | other *** search
/ NetNews Usenet Archive 1992 #20 / NN_1992_20.iso / spool / comp / lang / scheme / 2187 < prev    next >
Encoding:
Text File  |  1992-09-14  |  11.4 KB  |  233 lines

  1. Newsgroups: comp.lang.scheme
  2. Path: sparky!uunet!stanford.edu!snorkelwacker.mit.edu!bloom-beacon!INTERNET!dont-send-mail-to-path-lines
  3. From: jmiller@crl.DEC.COM
  4. Subject: What I learned from building Dylan/Thomas
  5. Message-ID: <9209142230.AA20554@peanut.crl.dec.com>
  6. Sender: daemon@athena.mit.edu (Mr Background)
  7. Reply-To: JMiller@crl.dec.com
  8. Organization: The Internet
  9. Date: Mon, 14 Sep 1992 22:30:40 GMT
  10. Lines: 221
  11.  
  12. I've been maintaining an unintentional "radio silence" for the last
  13. several weeks while working with Ron Weiss and Matt Birkholz to build
  14. the Thomas system.  Now that I'm out of that hole, I'd like to update
  15. others on some of what I learned in the process and respond to a few
  16. of the questions raised earlier on these mailing lists.
  17.  
  18. I. Despite all of the hairy stuff involving keywords, specializers,
  19. multiple inheritance and the rest of it, the generic dispatch system
  20. of Dylan (maybe also CLOS?) was remarkably simple to build and
  21. understand.  The key procedure is in the file generic.scm, and is
  22. simplified here (I removed support for multiple values, error
  23. checking, and renamed some functions for clarity):
  24.  
  25.    (define (generic-dispatch original-args generic-function)
  26.      (let ((applicable-methods
  27.         (sorted-applicable-methods
  28.          (generic-function.methods generic-function)
  29.          original-args)))
  30.        (let next-method-loop ((remaining-methods applicable-methods)
  31.                   (current-args original-args))
  32.      (apply (car remaining-methods)
  33.         (if (null? (cdr remaining-methods))
  34.             #F
  35.             (lambda (next-method . these-args)
  36.               (next-method-loop (cdr remaining-methods)
  37.                     (if (null? these-args)
  38.                         current-args
  39.                         these-args))))
  40.         current-args))))
  41.  
  42. This requires one non-trivial support routine:
  43.  
  44.   sorted-applicable-methods finds the applicable methods and sorts
  45.     them from most- to least-specific.  We used a topological sort of
  46.     all of the classes in the system.  This is used to provide an
  47.     ordering of the classes for comparing with individual arguments to
  48.     the method, which seems logical enough:
  49.  
  50.     (define (match-specializer? object specializer)
  51.       ;; Computes a distance value from the object to the specializer.
  52.       ;; This distance is either
  53.       ;;   #F  -- doesn't match the specializer
  54.       ;;    0  -- the specializer is a singleton for exactly this object
  55.       ;;   <n> -- ordering of the class representing this specializer in
  56.       ;;          the ordering of all classes (always >= 1)
  57.       (if (singleton? specializer)
  58.       (if (eq? object (singleton.object specializer)) 0 #F)
  59.       (if (subclass? (get-type object) specializer)
  60.           (class.generality specializer)
  61.           #F)))
  62.  
  63.     We then used lexicographic ordering to break ties for multiple
  64.     argument dispatch, which is specified in Dylan but seems basically
  65.     arbitrary.  This latter is a place where I'd expect a Scheme
  66.     system to say "unspecified".
  67.  
  68.   I won't reproduce the intervening code, but if you look for a while
  69.   you'll see that sorted-applicable-methods requires two things:
  70.  
  71.   a) A way, given a method under consideration, to find the
  72.      specializers that restrict the types of arguments to which it can
  73.      be applied.  In response to one of the questions on the Scheme
  74.      list, this is where the need for WEAK-CONS arises.  Since methods
  75.      are first-class in Dylan (Dylan/method <=> Scheme/lambda) and can
  76.      be attached at any time to a generic function, it is necessary at
  77.      runtime to be able to locate the specializers for a method.  This
  78.      requires building a data structure (an a-list or hash table, for
  79.      example) that uses methods as keys to locate their specializers.
  80.      But such a table would grow indefinitely in size unless the
  81.      garbage collector removes entries that correspond to methods that
  82.      have been eliminated from the system.  [There is a similar need
  83.      to locate a description of a generic function at runtime, used
  84.      for the error detection mechanism I eliminated from this
  85.      simplified code.]
  86.  
  87.   b) A mechanism for determining the most specific type of an object
  88.      at runtime.  The procedure "get-type" (in the file
  89.      runtime-top.scm) performs the operation.  It is a kludge and very
  90.      order sensitive.  I found this function rather unpleasant to
  91.      maintain as the system grew:
  92.  
  93.      (define (get-type obj)
  94.        (cond ((instance? obj) (instance.class obj))
  95.          ((number? obj)            ; Might be wrong
  96.           (if (real? obj)
  97.           (if (exact? obj)
  98.               (if (integer? obj)
  99.               <integer>
  100.               <ratio>)
  101.               <float>)
  102.           <complex>))
  103.          ((class? obj) <class>)
  104.          ((singleton? obj) <singleton>)
  105.          ((null? obj) <empty-list>)
  106.          ((slot? obj) <slot-descriptor>)
  107.          ((pair? obj) <pair>)
  108.          ((vector? obj) <simple-object-vector>)
  109.          ((string? obj) <byte-string>)
  110.          ((char? obj) <character>)
  111.          ((procedure? obj)
  112.           (cond ((dylan::generic-function? obj) <generic-function>)
  113.             ((dylan::method? obj) <method>)
  114.             (else <function>)))
  115.          ((keyword? obj) <keyword>)
  116.          ((symbol? obj) <symbol>)
  117.          (else <object>)))
  118.  
  119.      This observation has lead me back to a conversation we had about
  120.      the "portable record proposal" which went unresolved at our last
  121.      meeting.  The issue revolved around the pair of requirements for
  122.      disjointness of types and opacity of representation.  Several of
  123.      us discussed this at some length at the LISP conference and I
  124.      came away convinced of the need for a set of operations, separate
  125.      from the record package, for manufacturing disjoint user types.
  126.      At the Authors meeting, Jonathan Rees pointed out that he had
  127.      made such a proposal some time ago but that it had not been acted
  128.      upon.  I'd like to have us act on it: Jonathan, can you find it
  129.      and resubmit it?
  130.  
  131. II. On the question about teaching generic dispatch as opposed to
  132. object dispatch, I find myself for the first time disagreeing with
  133. Brian Harvey.  While I haven't actually tried to teach this material
  134. for two years now, I seem to recollect that I taught numbers very
  135. early in the course.  They are the one place in Scheme where generic
  136. operation is deeply built into the system, and it provides a simple
  137. and logical place to start when explaining generic dispatch.  The
  138. symbolic algebra system at the end of Chapter 2 of Structure and
  139. Interpretation takes this much farther, but perhaps at the expense of
  140. the gut-level understanding of the issue that comes from simply asking
  141. the question "How does Scheme do that with its numbers, anyway?".  The
  142. very simple example of adding integers to real numbers explains a lot
  143. to anyone familiar with representations.  For those unfamiliar with
  144. numerical representations, the introduction of generic sequence
  145. operations (like the ones in Dylan, but simplified) is another obvious
  146. starting point.  I find the INITIAL-STATE, NEXT-STATE, CURRENT-ELEMENT
  147. iteration protocol very elegant (although my taste would add
  148. CURRENT-KEY to all collections, not just explicit key sequences; even
  149. at the cost of an extra cons-cell for the state of a list).
  150.  
  151. I, personally, have always found the object dispatch method both
  152. limiting as a programming style and consequently rather unpleasant to
  153. teach.  Its main advantage, from what I've seen so far, is one of
  154. modularity.  The class heterarchy is conceptually elegant and works
  155. well, but unfortunately leads to a number of questions about where an
  156. object gets its behavior from, which inevitably leads to questions
  157. about HOW the object was implemented; this is precisely the
  158. abstraction violation that I thought object systems were intending to
  159. avoid.
  160.  
  161. III. I've written my first large, portable Scheme program.  It
  162. required exactly two extensions to the language (WEAK operations, a
  163. minimal error system), but is modularized in a way that makes the port
  164. easy.  I like this way of writing my programs, and I think it gives me
  165. a new way to think about the issue of portability.  The Scheme
  166. language has the flexibility to allow most reasonable programs to be
  167. divided into an implementation-independent part which calls procedures
  168. defined in a implementation-dependent part.  This isn't new; but this
  169. is the first time I've actually tried to make it work and it did!  The
  170. ease of porting this system from one Scheme to another contrasts
  171. rather sharply with my attempts to port MIT Scheme's implementation
  172. from one version of C to another, and to similar experiences in
  173. porting Pascal and BCPL programs in an earlier existence.
  174.  
  175. IV. To the Authors:
  176.  
  177. I'd really like to see some hooks into the garbage collector
  178. standardized.  This implementation experience, as well as a paper I
  179. wrote several years ago that touches on some of the other things one
  180. can do using a garbage collector, convinced me that it isn't going to
  181. be that hard to figure out "the right set".  I see two "must do" items
  182. and two optional ones:
  183.  
  184.    MUST DO 1: Have a way to specify a set of "precious" items which
  185.      the garbage collector is not at liberty to remove, but which it
  186.      will attempt to monitor and report when they are no longer
  187.      required for any other reason.  The two optional sets are
  188.      specific implementations of this idea which exist in several
  189.      current Scheme implementations, but which may not be the best
  190.      abstraction of the service.
  191.  
  192.    MUST DO 2: Have a way to explicitly invoke the equivalent of the
  193.      garbage collector's structure-preserving walk given an arbitrary
  194.      starting object.  One form this could take is a procedure that
  195.      takes the root object and a vector; it stores into the vector all
  196.      the items found from walking that object and returns a count of
  197.      all of the objects encountered (this allows the operation to
  198.      occur (conceptually, at least) without CONSing).  An alternate
  199.      formulation is to provide a function which takes an object and
  200.      returns a collection of the sub-components of that object
  201.      sufficient to allow a full structure walk from source-level
  202.      Scheme.
  203.  
  204.    OPTIONAL 1: Have a way to run a procedure at the end of every
  205.      garbage collection before returning to user code.  This is how
  206.      the semi-portable code in Thomas implements populations and one
  207.      dimensional "weak" tables on top of WEAK-CONS.  It is one way to
  208.      perform the report function in MUST DO 1, but not the only
  209.      possible way.  All three of the Schemes I used for Thomas (MIT,
  210.      Scheme->C, and Gambit) have some such hook, but all have slightly
  211.      different names and signatures for the function that installs the
  212.      GC daemon(s).
  213.  
  214.    OPTIONAL 2: The WEAK-CONS set of operations (WEAK-PAIR?, WEAK-CONS,
  215.      WEAK-C{A,D}R, SET-WEAK-C{A,D}R!) are now also available in all
  216.      three Scheme systems.  It took Joel Bartlett only one day to add
  217.      them to his conservative generational collector; they are nice
  218.      because of their simplicity.  They lack the generality of MUST DO
  219.      1, and would require something like the finalization proposal
  220.      that was circulated before our last meeting (but not discussed).
  221.  
  222. I'd also like to see some people take a good, hard look at the Thomas
  223. code for generic operations and the class hierarchy.  It is very
  224. portable and provides some very good capabilities.  I'd like someone
  225. to take an independent look at that code and see if it can be packaged
  226. nicely and put into the (putative) Scheme library.  If we can do that,
  227. we should be able to add a lot of the Thomas runtime system to the
  228. same Scheme library and have a very good start at a powerful
  229. programming library -- one thing that (IMHO) has long held up the
  230. adoption of Scheme as a "real" language.
  231.  
  232. --Jim
  233.