[comp.lang.scheme] Property lists in MIT Scheme.

mcr@Latour.Sandelman.OCUnix.On.Ca (Michael Richardson) (12/15/90)

  While bored between classes I started playing with building some
sort of symbolic differentiation program (I'm a physics student. I
hate differentiating things like spherical coordinates, etc...).
  TI-Scheme was around on some PCs in one lab so I started with that.
In my style of lisp, I use a lot of property lists --- my friendly
interaction loops usually say something like:
  (do ((cmd (read) (read)))
      ((eqv? cmd 'q))
     ((getprop cmd 'shell)))

  TI-Scheme has putprop/getprop.

  Now that exams have started, I've got around to compiling
MIT Scheme (6.1.2, Microcode 10.2, runtime 13.91, sf 3.13). I 
discovered to my amusement that property lists seem to be absent.
Okay, I says, is assq a primitive? Seems to be. No problem. 

  While grep'ing through the runtime code and microcode for 
references to property lists, I noticed a bunch of stuff on hash
functions, etc... Is there, (already existing?) some code to do 
property list-like things using hash codes? 
  I've read R^3S (quite awhile ago), but haven't haven't gotten
TeX up so I can print off the R^4S included in the docs. There is 
a hacked up copy of one of them (I don't remember which) with the 
TI-Scheme 'manuals' in the CS labs. They actually don't mention
property lists at all (that I remember).
  Were property lists dropped in scheme? Why?

[  I'm sorry, I'm new to Scheme --- but I've written some very
nice (I think) Lisp interpreters. My favorite one attached a 
'SUBRn' property to a symbol to give it a primitive definition.
The SUBRn symbol had a property called '*NUMBER-OF-ARGUMENTS*' that
told how many arguments this primitive received. One could define
new kinds of primitive invocation methods via similar methods. I
was working on a property list cache at the time... If this 
sounds like Anatomy of Lisp, it is. ]

  On a somewhat related note --- I believe that there is a 6.2
MIT Scheme no? Has anyone implemented the rational types? Does
6.2 have them? I read a comment here a couple of days ago stating 
that including rationals in the definition was a mistake. I'm 
curious as to why? Can anyone expand on this comment? 
  
  Please note Reply-To: if email'ing.
-- 
   :!mcr!:            |    The postmaster never          |  So much mail,
   Michael Richardson |            resolves twice.       |  so few cycles.
 mcr@julie.UUCP/michael@fts1.UUCP/mcr@doe.carleton.ca -- Domain address
    - Pay attention only to _MY_ opinions. -         registration in progress.

markf@zurich.ai.mit.edu (Mark Friedman) (12/19/90)

The current version of MIT Scheme (version 7.0) has a generalization
of the Lisp property list mechanism called the Association Table.  The
new version of MIT Scheme (which will be available soon) includes an
implementation of rational numbers.  Thr biggest mistake that I can
see with including rationals is illustrated by the following sometimes
frustrating transcript :-)

(/ 27 157)
;Value: 27/157

That is, if you want to use Scheme as a calculator you must do:

(exact->inexact (/ 27 157))
;Value: .17197452229299362

The following is some documentation on the MIT Scheme association
mechanism.

-Mark

---------------------------------------------------------------------
The Association Table
=====================

   MIT Scheme provides a generalization of the property list
mechanism found in most other implementations of Lisp.  MIT Scheme
uses weak pairs (*note Weak Pairs::.) to implement rapid access to a
global two-dimensional "association table".  This table is indexed by
two keys, called X-KEY and Y-KEY in the following procedure
descriptions.  These keys and the value associated with them can be
arbitrary objects.  Scheme uses `eq?' to compare keys to the key
values stored in the table.

   You can think of the association table as a matrix; you can access
a single value using both keys, a column using X-KEY only, and a row
using Y-KEY only.

   Note: because these procedures accept arbitrary objects as keys,
they are less efficient than their property-list counterparts in
other Lisps.  This is because they must perform an extra association
to find the stored value.

 * procedure+: 2d-put! X-KEY Y-KEY VALUE
     Associates VALUE with X-KEY and Y-KEY, and returns an
     unspecified value.  To retrieve this association, use `2d-get',
     `2d-get-alist-x', and `2d-get-alist-y'.  Notice that X-KEY and
     Y-KEY can be arbitrary objects (unlike other Lisp systems where
     they must be symbols).

 * procedure+: 2d-get X-KEY Y-KEY
     Returns the entry associated with X-KEY and Y-KEY by `2d-put!';
     returns `#f' if no such association exists.

 * procedure+: 2d-get-alist-x X-KEY
     Returns an association list (a list of pairs of key-value
     associations) of all entries made by `2d-put!' with X-KEY as its
     first argument.  Returns the empty list if no entries for X-KEY
     exist.

     For example,

          (2d-put! 'foo 'bar 5)
          (2d-put! 'foo 'baz 6)
          (2d-get-alist-x 'foo)                   =>  ((baz . 6) (bar . 5))

 * procedure+: 2d-get-alist-y Y-KEY
     Returns an association list (a list of pairs of key-value
     associations) of all entries made by `2d-put!' with Y-KEY as its
     second argument.  Returns the empty list if there are no entries
     for Y-KEY.

     For example,

          (2d-put! 'bar 'foo 5)
          (2d-put! 'baz 'foo 6)
          (2d-get-alist-y 'foo)                   =>  ((baz . 6) (bar . 5))

 * procedure+: 2d-remove! X-KEY Y-KEY
     Removes an association made by `2d-put!'.  Returns `#t' if an
     entry was found and removed, or `#f' if there was no such entry.


1D Tables
=========

   "1D tables" ("one dimensional" tables) are a generalization of
association lists.  In a 1D table, unlike an association list, the
keys of the table are held weakly.  1D tables are implemented by the
"weak pair" datatype (*note Weak Pairs::.).

   1D tables can often be used as a higher performance alternative to
the two dimensional association table (*note The Association
Table::.).  If one of the keys being associated is a compound object
such as a vector, the 1D table can be stored in one of the vector's
slots.  Under these circumstances, accessing items in the 1D table
will be comparable in performance to using a property list in a
conventional Lisp.

 * procedure+: make-1d-table
     Creates and returns a new, empty, 1D table.

 * procedure+: 1d-table? OBJECT
     Returns `#t' if OBJECT is a 1D table, otherwise returns `#f'.

 * procedure+: 1d-table/put! 1D-TABLE KEY VALUE
     Creates an association between KEY and VALUE in 1D-TABLE.  The
     value is unspecified.

 * procedure+: 1d-table/get 1D-TABLE KEY DEFAULT
     If 1D-TABLE contains an association for KEY, returns its value. 
     Otherwise returns DEFAULT.

 * procedure+: 1d-table/lookup 1D-TABLE KEY IF-FOUND IF-NOT-FOUND
     An alternative method for looking up values in a 1D table. 
     IF-FOUND must be a procedure of one argument, and IF-NOT-FOUND
     must be a procedure of no arguments.  If 1D-TABLE contains an
     association for KEY, IF-FOUND is invoked on the value of the
     association.  Otherwise, IF-NOT-FOUND is invoked with no
     arguments.

 * procedure+: 1d-table/remove! 1D-TABLE KEY
     Removes any association for KEY in 1D-TABLE and returns an
     unspecified value.


Hashing
=======

   The MIT Scheme object hash facility provides a mechanism for
generating a unique hash number for an arbitrary object.  This hash
number, unlike an object's address, is unchanged by garbage collection.

 * procedure+: object-hash OBJECT
 * procedure+: object-unhash K
     `object-hash' associates a non-negative exact integer with
     OBJECT and returns that integer.  If `object-hash' was
     previously called with OBJECT as its argument, the integer
     returned is the same as was returned by the previous call. 
     `object-hash' guarantees that distinct objects (in the sense of
     `eq?') are associated with distinct integers.

     `object-unhash' takes a non-negative exact integer K and returns
     the object associated with that integer if there is one,
     returning `#f' otherwise.  In other words, if `object-hash'
     previously returned K for some object, that object is the value
     of the call to `object-unhash'.

     An object that is passed to `object-hash' as an argument is not
     protected from being garbage collected.  If all other references
     to that object are eliminated, the object will be garbage
     collected; subsequently calling `object-unhash' with the hash
     number of the (garbage-collected) object will return `#f'.

          (define x (cons 0 0))                   =>  unspecified
          (object-hash x)                         =>  77
          (eqv? (object-hash x) (object-hash x))  =>  #t
          (define x 0)                            =>  unspecified
          (gc-flip)                               ;force a garbage collection
          (object-unhash 77)                      =>  #f

     It is useful to know that objects whose external representation
     begins with the character sequence `#[' almost always print
     their hash number as a part of the representation.  This can be
     an invaluable debugging aid.  The format of this representation
     is:

          #[TYPE HASH OTHER-STUFF]

     Here TYPE is the name of the object's datatype, for example,
     `procedure' or `uninterned-symbol'; HASH is the hash number for
     that object, and OTHER-STUFF differs depending on the object's
     datatype (often it is empty).

          (define (f x) (+ x 2))
          f                               =>  #[compound-procedure 84 f]
          (object-hash f)                 =>  84
          (object-unhash 84)              =>  #[compound-procedure 84 f]

     Note: `hash' is a synonym for `object-hash'; and `unhash' is a
     synonym for `object-unhash'.  These synonyms are obsolete and
     should not be used.


Weak Pairs
==========

   "Weak pairs" are a mechanism for building data structures that
point at objects without protecting them from garbage collection. 
The car of a weak pair holds its pointer weakly, while the cdr holds
its pointer in the normal way.  If the object in the car of a weak
pair is not held normally anywhere else, it will be garbage collected.

   Note: weak pairs are *not* pairs; that is, they do not satisfy the
predicate `pair?'.

 * procedure+: weak-pair? OBJECT
     Returns `#t' if OBJECT is a weak pair; otherwise returns `#f'.

 * procedure+: weak-cons CAR CDR
     Allocates and returns a new weak pair, with components CAR and
     CDR.  The component CAR is held weakly.

 * procedure+: weak-pair/car? WEAK-PAIR
     This predicate returns `#f' if the car of WEAK-PAIR has been
     garbage collected.  In other words, this operation is true if
     WEAK-PAIR has a valid car component.

     Normally, this procedure is used to determine if `weak-car'
     would return a valid value.  An obvious way of using this
     procedure would be:

          (if (weak-pair/car? x)
              (weak-car x)
              ...)

     However, since a garbage collection could occur between the call
     to `weak-pair/car?' and `weak-car', this would not always work
     correctly.  Instead, the following should be used, which always
     works:

          (or (weak-car x)
              (and (not (weak-pair/car? x))
                   ...))

     The reason that the latter expression works is that `weak-car'
     returns `#f' in just two instances: when the car component is
     `#f', and when the car component has been garbage collected.  In
     the former case, if a garbage collection happens between the two
     calls, it won't matter, because `#f' will never be garbage
     collected.  And in the latter case, it also won't matter (the
     object will not become un-collected!).

 * procedure+: weak-car WEAK-PAIR
     Returns the car component of WEAK-PAIR.  Use the predicate
     `weak-pair/car?' to determine if there is such a component.  If
     the car component has been garbage collected, this operation
     returns `#f' (but it can also return `#f' if that is the value
     that was stored in the car).

     See `weak-pair/car?' for an example showing how to use this
     operation correctly.

 * procedure+: weak-set-car! WEAK-PAIR CAR
     Sets the car component of WEAK-PAIR to CAR.

 * procedure+: weak-cdr WEAK-PAIR
 * procedure+: weak-set-cdr! WEAK-PAIR CDR
     These operations manipulate the cdr component of WEAK-PAIR in
     the usual way.  Since the cdr is held in the normal way, they
     work identically to `cdr' and `set-cdr!', respectively.


--

Mark Friedman
MIT Artificial Intelligence Lab
545 Technology Sq.
Cambridge, Ma. 02139

markf@zurich.ai.mit.edu

cph@ALTDORF.AI.MIT.EDU (Chris Hanson) (12/28/90)

   Date: 15 Dec 90 07:54:21 GMT
   From: Michael Richardson <snorkelwacker.mit.edu!apple!julius.cs.uiuc.edu!zaphod.mps.ohio-state.edu!van-bc!ubc-cs!news-server.csri.toronto.edu!utgpu!cunews!micor!latour!mcr@bloom-beacon.mit.edu>

     Now that exams have started, I've got around to compiling
   MIT Scheme (6.1.2, Microcode 10.2, runtime 13.91, sf 3.13). I 
   discovered to my amusement that property lists seem to be absent.
   Okay, I says, is assq a primitive? Seems to be. No problem. 

Property lists are available in MIT Scheme, with slightly different
names:

    putprop		2d-put!
    getprop		2d-get
    remprop		2d-remove!

The names were changed because these property lists differ in some
important respects from those of Lisp:

* The keys can both be any object -- it not necessary that one be a
  symbol.

* The keys are not protected from garbage collection -- if one of the
  keys is reclaimed, the property is automatically removed from the
  table.

* The performance is somewhat worse -- two linear-time lookups instead
  of one.

     While grep'ing through the runtime code and microcode for 
   references to property lists, I noticed a bunch of stuff on hash
   functions, etc... Is there, (already existing?) some code to do 
   property list-like things using hash codes? 

The hash-code stuff is used for hash tables, not property lists.

     I've read R^3S (quite awhile ago), but haven't haven't gotten
   TeX up so I can print off the R^4S included in the docs. There is 
   a hacked up copy of one of them (I don't remember which) with the 
   TI-Scheme 'manuals' in the CS labs. They actually don't mention
   property lists at all (that I remember).
     Were property lists dropped in scheme? Why?

Property lists were never part of the Scheme reports.

     On a somewhat related note --- I believe that there is a 6.2
   MIT Scheme no? Has anyone implemented the rational types? Does
   6.2 have them?

6.2 doesn't have rationals.  Release 7.1, entering beta test in a few
days, does.

      :!mcr!:            |    The postmaster never          |  So much mail,
      Michael Richardson |            resolves twice.       |  so few cycles.
    mcr@julie.UUCP/michael@fts1.UUCP/mcr@doe.carleton.ca -- Domain address
       - Pay attention only to _MY_ opinions. -         registration in progress.