Subject: Re: looking for a language with any of the following 4 charachteristics  (all 4 would be nice).
From: Erik Naggum <erik@naggum.net>
Date: Fri, 15 Feb 2002 07:37:45 GMT
Newsgroups: comp.lang.lisp
Message-ID: <3222747467111424@naggum.net>

* "Wade Humeniuk" <humeniuw@cadvision.com>
| I think hash-tables are meant to store larger amounts of unordered data and
| I cannot see using the :initial-contents to populate a 1000+ key/value pairs
| (or for that matter 100,000 values).  I would rebel at doing the typing.
| For me, storing 2 key/value or even 100 values in a hash table is just not
| worth it.

  When a hash-table is all you have, you tend to optimize it heavily.  The
  same goes for regexps.  Both hash-tables and regular expressions are very
  powerful and general tools, but they turn into monstrosities when all the
  more specialized tools are effectively removed from the language they use.

  Common Lisp has the disadvantage compared to the one-trick languages that
  the different access functions for various kinds of mappings between key
  and value are precisely that -- different.  When there is only one way to
  do it, being offered several ways is just confusing, and so people who
  think "hash-table" when they should have thought "key-value mapping" will
  miss simple and elegant things like property lists or association lists.

  I mean, if { "foo" => "bar", "baz" => "zot" } is so great, why is not
  (("foo" . "bar") ("baz" . "zot"))?  Is it _only_ the sugar-coated syntax
  that is so visually appealing to some?  If so, a Common Lisp programmer
  will write a small parser function that is called via the reader-macro
  support to read this list.  Why is this not more used?  I belive it is
  because most people who pine for other syntaxes have no clue how to go
  about specifying them or writing parsers for them, and because once you
  have grown that clue, you do not need it or, more importantly, want it.

  But what the heck.  Proof of concept follows.  

(let ((mapping-types ()))
  (defun readtable-mapping-type (readtable)
    (check-type readtable readtable)
    (or (cdr (assoc readtable mapping-types)) :alist))

  (defun (setf readtable-mapping-type) (mode readtable)
    (check-type readtable readtable)
    (check-type mode (member :alist :plist :eq :eql :equal :equalp))
    (let ((existing (assoc readtable mapping-types)))
      (if existing
	  (setf (cdr existing) mode)
	(push (cons readtable mode) mapping-types))))

  (defun mapping-reader (stream char)
    (declare (stream stream) (ignore char))
    (loop
	with mapping-type = (readtable-mapping-type *readtable*)
	with key and value
	with hashtable = (case mapping-type
			   (:eq     (make-hash-table :test #'eq))
			   (:eql    (make-hash-table :test #'eql))
			   (:equal  (make-hash-table :test #'equal))
			   (:equalp (make-hash-table :test #'equalp)))
	do
	  (when (eql #\} (peek-char t stream t nil t))
	    (read-char stream t nil t)
	    (loop-finish))
	  (setq key (read stream t nil t))
	  (if (eql #\= (peek-char t stream t nil t))
	      (read-char stream t nil t)
	    (error 'parse-error :stream stream))
	  (unless (eql #\> (read-char stream t nil t))
	    (error 'parse-error :stream stream))
	  (setq value (read stream t nil t))
	  (case (peek-char t stream t nil t)
	    (#\, (read-char stream t nil t))
	    (#\} t)
	    (t (error 'parse-error :stream stream)))
	if (eq mapping-type :alist) collect (cons key value) into list else
	if (eq mapping-type :plist) collect key into list
	   and collect value into list else
	do (setf (gethash key hashtable) value)
	finally (case mapping-type
		  ((:alist :plist) (return (list 'quote list)))
		  (t (return hashtable))))))

  Now, suppose the above is available in the Common Lisp world.  A file
  that uses this syntax could go like this:

(eval-when (:execute :compile-toplevel)
  (setq *readtable* (copy-readtable))
  (set-macro-character #\{ #'mapping-reader)
  (set-syntax-from-char #\} #\)))

(eval-when (:execute :compile-toplevel)
  (setf (readtable-mapping-type *readtable*) :alist))

(defparameter *roman-alist*
    { "I" => 1,
      "V" => 5,
      "X" => 10,
      "L" => 50,
      "C" => 100,
      "D" => 500,
      "M" => 1000 })

(eval-when (:execute :compile-toplevel)
  (setf (readtable-mapping-type *readtable*) :plist))

(defparameter *roman-plist*
    { "I" => 1,
      "V" => 5,
      "X" => 10,
      "L" => 50,
      "C" => 100,
      "D" => 500,
      "M" => 1000 })

(eval-when (:execute :compile-toplevel)
  (setf (readtable-mapping-type *readtable*) :equal))

(defparameter *roman-hash*
    { "I" => 1,
      "V" => 5,
      "X" => 10,
      "L" => 50,
      "C" => 100,
      "D" => 500,
      "M" => 1000 })

  I chose this design because I figured that it would be annoying to change
  the data if the access functions would need to change and that it would
  be more convenient to specify this via a readtable-local setting than add
  a lot more syntax for it.  If you have support for the in-syntaax form
  and named readtables, creating a named readtable to invoke this syntax
  would obviously be the best solution.

  Now, if you want other kinds of things to go on within { }, it is not a
  lot of trouble to build a parser by hand, provided you are not a moron
  and create syntaxes the like of C++ or some other horrible monstrosity.

  Creating a writer for alists, plists, and hashtables that write data in
  this syntax is left as an exercise for the reader.

  Copyright notice: Copyright © 2002 Erik Naggum.  The code may be used to
  create commercial products without charge or other license provided that
  the author is credited in each source file that uses it or the syntax it
  allows.  Creation of derivative works is permitted under two conditions:
  No rewrites to use if* or not to use loop are allowed, and the author
  must be notified by e-mail at <erik@naggum.net>.  Search engines are
  permitted to index and retrieve this article in perpetuity and present to
  users in either normal text or html-ized form, but no other form of re-
  publication is permitted.  Inclusion of this material in some "cookbook"
  is expressly not permitted.

///
-- 
  In a fight against something, the fight has value, victory has none.
  In a fight for something, the fight is a loss, victory merely relief.