[comp.lang.scheme] Destructuring / pattern-matching

jeff@aiai.edinburgh.ac.UK (Jeff Dalton) (04/11/91)

> Date: Thu, 4 Apr 91 13:01:00 PST
> From: Jon L White <jonl%kuwait@lucid.com>
> Message-Id: <9104042101.AA28583@kuwait>
> To: Scheme@mc.lcs.mit.edu

> In fact, Lisp/370 (predecessor to IBM's "Uncommon Lisp" called
> LISP/VM) had automatic destructuring in every lambda argument
> position, including the input arguments.  By 1980, such an 
> extension was working in parts of MacLisp and in VAX/NIL.  In
> fact, it even "destructured" over vectors as well as lists;
> [...]

> This was particularly useful in the VAX/NIL compiler and assembler
> where much more use was made of simple vectors rather than lists.  
> We even had a version that would destructure over DEFSTRUCT instances,
> in the expectation that "objects" like vectors and structures would 
> displace lists as the more common data structure "of choice".

I think this is an excellent example of how language design can affect
the program development and debugging.  (Which is not to say that many
of the problems couldn't be solved by changing the development
environment rather than the language.)

One problem with destructuring (as opposed to pattern matching) is
that it may not bother to check whether the object being destructured
has the right shape.  Of course, the corresponding problem with
pattern matching is that it makes the check even when it is
unnecessary.

Another problem with patterns in general is that they usually do not
respect the abstract structure of objects.  If something is the third
element of a list, a pattern will depend on it being the third
element; if something is a slot of a struct, the pattern will depend
on it being a slot (rather than being accessed some other way).  This
is a serious hassle in Prolog, where changing the structure of a term
often requires changing patterns all over the program; and since the
contents of terms are accessed positionally, it's easy to make a
mistake.

When people talk of the advantages of pattern-matching in Prolog and
functional languages, they seldom mention that it has this disadvantage
too.

On the other hand, Lisp programmers often fail to make all the
appropriate checks when examining a list.  One often sees code
that does something like (EQ (CAR EXPR) 'LAMBDA) and then assumes
that the entire list is a proper lambda-expression, even when
the list is accessed abstractly by calling functions such as
LAMBDA-EXPR-PARAMETER-LIST (which just does a CADR).

This suggests that it may be a good idea to have both destructuring
and pattern-matching in Lisp, especially if something can be done
about the problems mentioned above.

Fortunately, something can be done (or so I'm told).  For example, in
functional languages it may be possible to define abstract "views" of
objects for use in patterns.  In Prolog, it may be possible to use
code unfolding (read macro expansion) to define structures and
patterns abstractly.  But not all languages or implementations have
such facilities, and (in Prolog at least), most programmers do not use
them.

This suggests that it helps to make such features a standard part of
the language and so encourage their use.  However, there are so many
different ways to express patterns, with different advantages and
disadvantages, that it is not clear that s single mechanism should
be chosen.  As JonL mentioned:

> There was no technical difficulty in extending these ideas for Common 
> Lisp; but instead a debate arose over whether the pattern should be 
> defined to be a data object (i.e., a cons cell implying taking the CAR 
> and CDR of the input argument) or to be a program [i.e., for the above
> example, write `(,a `#(,b ,c) . ,l) instead of (a #(b c) . l)].

The obvious answer in Lisp seems to be: write a macro to do whatever
pattern-matching or destructuring you want.  Unfortunately, it seems
to be sufficiently hard to design a good macro that programmers often
write out what they want by hand instead, even when they can make use
of DESTRUCTURING-BIND.  JonL writes:

> I'm always having to intersperse lines of LET's amongst multiple
> incarnations of DESTRUCTURING-BIND, as well as with MULTIPLE-VALUE-BIND's.

Nonetheless, it should be possible for those so inclined to write
_some_ macros.  But then we often encounter another problem.  For
example, I wrote some macros that let me do such things as

   (defpat
     ((app () y)          y)
     ((app (cons a x) y)  (cons a (app x y))))

In the Scheme version I could also write

   (defpat
     ((app `() y)          y)
     ((app `(,a . ,x) y)   `(,a . ,(app x y))))

All I had to do was tell my macro how to compile (some cases of)
quasiquote.  But it's a pain to do this for Common Lisp, because
backquote can expand into so many different things.  Indeed, Common
Lisp often fails to define what happens at the level just below the
one you're meant to use, which makes it hard to define facilities
that are similar but not identical to those offered as standard.
GJC made this point about a month ago:

   From: gjc@mitech.COM
   Newsgroups: comp.lang.scheme
   Subject: Dear JONL: an ode to SQUID.
   Message-ID: <9103121148.AA07598@schizo>
   Date: 11 Mar 91 11:21:06 GMT

   The lack of SQUID and the foolishness of the "#," approach first
   struck me when dealing with the port of the FORTRAN->LISP
   translator from MACLISP to COMMON-LISP.

   By "#," approach I mean a tendency, first seen in the LISPMACHINE
   at MIT, to avoid an *OPEN* implementation policy, generally a
   SEMANTICS FIRST, then SYNTAX, has a tendency to be the most open,
   and instead go as soon as possible to some concept limited by the
   imagination of the lisp implementor of *EXACTLY* what the user will
   *REQUIRE*.

   One result is that many significantly complex applications, e.g.
   Macsyma, Fortran->Lisp, generally any imbedded language, depended
   critically on internal undocumented representations (e.g. the
   readmacro expansion of "#," and internal flags such as
   SI:COMPILER-XYX-FLAG) so as to be frustratingly fragile to systems
   changes.

Nonetheless, I think the best way to proceed would be for people to
write macros that they find useful, let others use them, and see what
solutions evolve. 

-- jeff

davis@barbes.ilog.fr (Harley Davis) (04/12/91)

In article <12542.9104111347@subnode.aiai.ed.ac.uk> jeff@aiai.edinburgh.ac.UK (Jeff Dalton) writes:

   > From: Jon L White <jonl%kuwait@lucid.com>

   > This was particularly useful in the VAX/NIL compiler and assembler
   > where much more use was made of simple vectors rather than lists.  
   > We even had a version that would destructure over DEFSTRUCT instances,
   > in the expectation that "objects" like vectors and structures would 
   > displace lists as the more common data structure "of choice".

   The obvious answer in Lisp seems to be: write a macro to do whatever
   pattern-matching or destructuring you want.  Unfortunately, it seems
   to be sufficiently hard to design a good macro that programmers often
   write out what they want by hand instead, even when they can make use
   of DESTRUCTURING-BIND.  JonL writes:

   > I'm always having to intersperse lines of LET's amongst multiple
   > incarnations of DESTRUCTURING-BIND, as well as with MULTIPLE-VALUE-BIND's.

   Nonetheless, I think the best way to proceed would be for people to
   write macros that they find useful, let others use them, and see what
   solutions evolve. 

I think a simple macro solution is largely sufficient.  For example,
we have a simple DESTRUCTURING-LET macro in our Lisp.  In
approximately 60,000 lines of Lisp kernel code, there are exactly four
uses of this macro.  We avoid extensive use of such a feature because
there are very few list structures being passed around which need to
be destructured.  If we need to look at certain object components
within a function, it is best to use accessors to avoid dependency on
implementations of objects using particular slot names; in this case,
LET is sufficient.

-- Harley Davis
--
------------------------------------------------------------------------------
nom: Harley Davis			ILOG S.A.
net: davis@ilog.fr			2 Avenue Gallie'ni, BP 85
tel: (33 1) 46 63 66 66			94253 Gentilly Cedex, France

jonl%kuwait@lucid.COM (Jon L White) (04/13/91)

In isssue Scheme Digest V3 #184, Jeff Dalton recounts the ways that 
LET, DESTRUCTURING-BIND, and MULTIPLE-VALUE-BIND could be unified
by a single user-defined macro:

    Nonetheless, I think the best way to proceed would be for people to
    write macros that they find useful, let others use them, and see what
    solutions evolve. 

I know this sort of topic has already been flamed-out on this list, but 
the irresitable force of macro power does come up against the immovable 
object of a community of programmers.  That is to say, as long as I was 
working only by myself, or with a VERY small set of hackers, Jeff's 
suggestion for extra macrofication works fine.  But somehere around 
the 20-person "industrial quality" project you find that the number 
of idiosyncratic, personalized syntax macros is too large for anyone 
to comprehend.  It just takes one conservative member of the team to 
demand that no macros be used for trivial extensions; and this rules 
out the one-liner JONL-BIND in favor of the three-lines involving LET, 
DESTRUCTURING-BIND, and MULTIPLE-VALUE-BIND.

The above paragraph is based on hard experience (my own) with a group
considerably smaller than 20.

Of course, it is easier to bear if all 20 persons believe that the
macro in question is about to be "a standard" in the language.  (This
may explain why there are so many constructs being suggested for
standardization; but let us drink no standards before their time.)



-- JonL --

ok@goanna.cs.rmit.oz.au (Richard A. O'Keefe) (04/15/91)

In article <12542.9104111347@subnode.aiai.ed.ac.uk>, jeff@aiai.edinburgh.ac.UK (Jeff Dalton) writes:
> Another problem with patterns in general is that they usually do not
> respect the abstract structure of objects.  If something is the third
> element of a list, a pattern will depend on it being the third
> element; if something is a slot of a struct, the pattern will depend
> on it being a slot (rather than being accessed some other way).  This
> is a serious hassle in Prolog, where changing the structure of a term
> often requires changing patterns all over the program; and since the
> contents of terms are accessed positionally, it's easy to make a
> mistake.

> When people talk of the advantages of pattern-matching in Prolog and
> functional languages, they seldom mention that it has this disadvantage
> too.

(Self-advertisement:  "The Craft of Prolog" _does_ talk about this and
what to do about it.)

A possible answer in Lisp-like languages would be to introduce a
new form

	(define-pattern (<name> <parameter>...)
		<simple backquote expression>)

where <simple backquote expressions> are sufficiently restricted for
a simple program to figure out how to invert them (say: each
<parameter> name must appear exactly once in the pattern, ,@ may appear
only at the end of a list, something like that).  One also introduces a
new , variant:  ,=(<pattern name> <backquote expression>...).

If I used
	(define-pattern (assoc-link Key Val Rest)
	    `((,Key ,@Val) ,@Rest))

then
	(let ((foo `,=(assoc-link foo 9 ,bar)))
	    ...)
would have the effect of
	(let ((foo `((foo . 9) ,@bar)))
	    ...)
and
	(destructure `,=(assoc-link ,k ,v ,bar) foo
	    ...)
would have the effect of
	(destructure `((,k ,@v) ,bar) foo
	    ...)

It's certainly doable.
-- 
It is indeed manifest that dead men are formed from living ones;
but it does not follow from that, that living men are formed from dead ones.
			-- Tertullian, on reincarnation.

mkatz@garlic.stanford.EDU (Morris Katz) (04/15/91)

   Date: Fri, 12 Apr 91 18:36:05 PDT
   From: Jon L White <jonl%kuwait@lucid.com>

   In isssue Scheme Digest V3 #184, Jeff Dalton recounts the ways that 
   LET, DESTRUCTURING-BIND, and MULTIPLE-VALUE-BIND could be unified
   by a single user-defined macro:

       Nonetheless, I think the best way to proceed would be for people to
       write macros that they find useful, let others use them, and see what
       solutions evolve. 

   I know this sort of topic has already been flamed-out on this list, but 
   the irresitable force of macro power does come up against the immovable 
   object of a community of programmers.  That is to say, as long as I was 
   working only by myself, or with a VERY small set of hackers, Jeff's 
   suggestion for extra macrofication works fine.  But somehere around 
   the 20-person "industrial quality" project you find that the number 
   of idiosyncratic, personalized syntax macros is too large for anyone 
   to comprehend.  It just takes one conservative member of the team to 
   demand that no macros be used for trivial extensions; and this rules 
   out the one-liner JONL-BIND in favor of the three-lines involving LET, 
   DESTRUCTURING-BIND, and MULTIPLE-VALUE-BIND.

I sure hope they don't feel the same way about procedures.  :-)  If not, I
think that they should consider for a while why procedural abstraction is
acceptable, but abstration using macros is not.  I suspect that the answer has
more to do with documentation conventions and other development environment
considerations than anything to do with language semantics, per se.  If my
suspicion is correct, then I advocate fixing our tools rather than changing the
language. 

   The above paragraph is based on hard experience (my own) with a group
   considerably smaller than 20.

   Of course, it is easier to bear if all 20 persons believe that the
   macro in question is about to be "a standard" in the language.  (This
   may explain why there are so many constructs being suggested for
   standardization; but let us drink no standards before their time.)

The logical extension of this argument is CommonLisp.  Dump so much in the
language that noone can ever imagine extending it in any way.  If you do find a
way, just go to the CL standard committee and they will add it to the next
release.  :-)
--------------------
Morry Katz
katz@cs.stanford.edu
--------------------

jonl%kuwait@lucid.COM (Jon L White) (04/16/91)

re:  It just takes one conservative member of the team to 
       demand that no macros be used for trivial extensions; and this rules 
       out the one-liner JONL-BIND in favor of the three-lines involving LET, 
       DESTRUCTURING-BIND, and MULTIPLE-VALUE-BIND.

    I sure hope they don't feel the same way about procedures.  :-)  If not, I
    think that they should consider for a while why procedural abstraction is
    acceptable, but abstration using macros is not.   . . . 

I wouldn't be so sure about this.  The problem is one of complexity --
current buzzword: MegaProgramming -- and not of whether the interface
is procedureal, functional, meta (macros and "hooks"), or what-not.


re: The logical extension of this argument is CommonLisp.  Dump so much in 
    the language that noone can ever imagine extending it in any way.  If 
    you do find a way, just go to the CL standard committee and they will 
    add it to the next release.  :-)

Since you mention the word "release", I must assume you are referring to
the history of the MIT Lisp Machine project, and its notable commercial
follow-on (Symblics), wherein new and often incompatible "releases" 
occured nearly every six months.  Indeed, in the time period between 
1978 and 1985, one could substantiate the charge you make; but the X3J13 
committee (for CL standards) has yet to make a "release".

So how did Common Lisp come by the reputation you mention (I am, sadly,
acknowledging a "ring of truth" if not complete factuality to your 
comment)?  First, recall that Common Lisp is the evolutionary extension
of a series of Lisps beginning with the historic "1.5" at MIT nearly
30 years ago  -- progressing through CTSS Lisp  (ever hear of the 
"Compatible Time Sharing System?"), PDP6 Lisp, PDP10 MacLisp,
LispMachineLisp, SPICELisp, and so on.  A bit of middle-aged fat is 
beginning to show.

But perhaps more importantly is the influence during the early 1980's
of the idea that "Lisp" is the world's best programming _environment_.
A parallel with the Unix world is (1) a computer language usually
known by it's one-letter name, (2) an enormous non-standard set of
library routines and utilities [although some standards bodies are 
addressing this question.]  It's probably too late for Common Lisp ever 
to go back to a pure "kernel" language idea, but some of the "stock 
hardware" vendors of Common Lisp implementations have indeed been 
trying to unbundle pieces of the MonsterLispMachine inheritance from 
the required part of a Lisp system.  [Contact me privately for details
on what is happening; or, contact your favorite vendor's salesman;
but please try to resist any intrusion of a "Marketing Blitzkrieg"
into this email list, as has already happend on some other lists.]


-- JonL --

boley@cs.umn.edu (Daniel Boley) (04/16/91)

Is this too simple a way to destructure a list of values?
Here we locally destructure the first 3 elements of the list
	multiple-values-as-a-list
into 3 local variables and as well as getting the cdddr in rest.

; first define this simple fcn.....
(define (give value form)    ; give value to a function
	(apply form value))

; use GIVE to destructure a list of multiple values...
(give multiple-values-as-a-list
      (lambda (variable-1 variable-2 variable-3 . rest)
	      expr-to-evaled-with-destructured-bindings))

; A macro is unnecessary, but if you used a macro, it could look like...
; (this needs a better mnemonic name)
(give-macro (variable-1 variable-2 variable-3 . rest)
	    multiple-values-as-a-list
	    expr-to-be-evaled-with-destructured-bindings)

Of course, a single value handled this way will be a list of one item, so it
won't be as transparent as MULTIPLE-VALUE-BIND, yet it seems so simple.

By just extending the lambda form so that the list of formals can be a
list of lists, one can make this quite general...
- Dan Boley
  boley@cs.umn.edu

mkatz@garlic.stanford.EDU (Morris Katz) (04/16/91)

   Date: 15 Apr 91 10:37:38 GMT
   From: "Richard A. O'Keefe" <ok@goanna.cs.rmit.oz.au>
   Organization: Comp Sci, RMIT, Melbourne, Australia

   In article <12542.9104111347@subnode.aiai.ed.ac.uk>, jeff@aiai.edinburgh.ac.UK (Jeff Dalton) writes:
   > Another problem with patterns in general is that they usually do not
   > respect the abstract structure of objects.  If something is the third
   > element of a list, a pattern will depend on it being the third
   > element; if something is a slot of a struct, the pattern will depend
   > on it being a slot (rather than being accessed some other way).  This
   > is a serious hassle in Prolog, where changing the structure of a term
   > often requires changing patterns all over the program; and since the
   > contents of terms are accessed positionally, it's easy to make a
   > mistake.

   > When people talk of the advantages of pattern-matching in Prolog and
   > functional languages, they seldom mention that it has this disadvantage
   > too.

   (Self-advertisement:  "The Craft of Prolog" _does_ talk about this and
   what to do about it.)

   A possible answer in Lisp-like languages would be to introduce a
   new form

	   (define-pattern (<name> <parameter>...)
		   <simple backquote expression>)

   . . .

For another pattern matching extension to Scheme see Erik Ruf's Stanford
Computer Systems Lab Tech report "LogScheme: Integrating Logic Programming into
Scheme" an earlier draft of which appeared in the proceedings of the 4th ACM
Conference on Functional Programming Languages and Computer Architecture under
the title "Nondeterminism and Unification in LogScheme".
--------------------
Morry Katz
katz@cs.stanford.edu
--------------------

jeff@aiai.edinburgh.ac.UK (Jeff Dalton) (04/17/91)

I wrote:

   Another problem with patterns in general is that they usually do
   not respect the abstract structure of objects.

Richard O'Keefe suggested a form

   (define-pattern (<name> <parameter>...)
        <simple backquote expression>)

I've done something slightly different.  I wanted to try patterns
similar to those in functional languages where, say, (CONS A B) can be
used as a pattern that mateches a pair, binding A to the car and B to
the CDR.  So I have a form

   (define-view <constructor> (<accessor> ...))

The view for a pair would be 

   (define-view cons (car cdr))

But neither solution feels entirely "right" to me, which makes me
think (hope?) better solutions must be possible.  That's one reason
I felt 

   the best way to proceed would be for people to write macros that
   they find useful, let others use them, and see what solutions
   evolve.

To which JonL replied:

   I know this sort of topic has already been flamed-out on this list, but 
   the irresitable force of macro power does come up against the immovable 
   object of a community of programmers.  That is to say, as long as I was 
   working only by myself, or with a VERY small set of hackers, Jeff's 
   suggestion for extra macrofication works fine.  But somehere around 
   the 20-person "industrial quality" project you find that the number 
   of idiosyncratic, personalized syntax macros is too large for anyone 
   to comprehend.

And later:

   The problem is one of complexity -- current buzzword: MegaProgramming
   -- and not of whether the interface is procedureal, functional,
   meta (macros and "hooks"), or what-not.

I would, of course, agree that it would be a mistake for programmers
working together on a project to use personal, idiosyncratic macros.
But good macros should decrease complexity, and it is still worth
asking: how do we get better macros?  This is where the "evolution"
comes in.  The early stanges, at laast, can't happen in industrial
projects, but those aren't the only programs being written.

Indeed, some interesting results have already appeared.  For example,
we have extend-syntax for defining macros and the various iteration
macros described in CLtL II.  We can't yet tell whether they, or their
successors, will ever be readily accpted in industrial projects; but I
still think the development of better macros is an effort worth
making.

-- jeff