multiple-value binding let and let* 
Author Message
 multiple-value binding let and let*

http://www.*-*-*.com/ ~stickel/mvlet.lisp
contains macros for MVLET and MVLET* that extend the LET and LET*
forms to include bindings of the form

((:values var1 var2 var*) [init-form])
((:list   var1 var2 var*) [init-form])
((:list*  var1 var2 var*) [init-form])

This does not include multilevel or lambda-list destructuring (which I
think might be too complex functionality to add to let), but satisfies
my desire for binding multiple results from a function when the
results are returned either as multiple values or in a list.



Sat, 02 Feb 2002 03:00:00 GMT  
 multiple-value binding let and let*

Quote:

> http://www.ai.sri.com/~stickel/mvlet.lisp
> contains macros for MVLET and MVLET* that extend the LET and LET*
> forms to include bindings of the form
> ((:values var1 var2 var*) [init-form])
> ((:list   var1 var2 var*) [init-form])
> ((:list*  var1 var2 var*) [init-form])

One thing that occurred to me about these complex multipurpose
LET-like syntaxes being proposed is that, if they are intended
eventually to replace LET, then one needs to think about the status of
LET itself. If they are *not* then the remainder of this article is
not particularly interesting, but I think that they are intended so to
do at least be some proposers.

LET is currently a special operator in CL.  But these extensions would
make it much more complex than any other special operator in the
language.  Further, some of these extensions are naturally defined in
terms of other operators (MULTIPLE-VALUE-BIND for instance), which are
themselves in fact macros.

So really, a LET extended like this should not be a special operator.
If there are problems with its being written as a macro (which I think
must only be declaration handling), then they should be fixed so it
can be.  So, there's then a choice of making LET not be special at all
-- it can be defined as a macro easily enough (I think!), or of
providing some kind of primitive SUBLET which just did what LET does
now, and was special.

I presume the reasons that LET is defined as special now would still
hold, so there should still be some special operator provided to do
the basic variable-binding thing.

--tim



Sun, 03 Feb 2002 03:00:00 GMT  
 multiple-value binding let and let*
this syntax is very similar to what I suggested recently.  I think it
can be done better, and even w/o keywords, but this is nice, for now.
People can experiment with it, and see if they like it.

thanks for opening it up.



Sun, 03 Feb 2002 03:00:00 GMT  
 multiple-value binding let and let*
The reasons why LET is a special operator are valid, though I've seen
a macro version of let using LAMBDA:

(let ((x 2)
      (y 3))
  (declare ...)
  <body>)

==

(funcall #'(lambda (x y)
             (declare ...)
             <body>)
         2 3)

and so on.  Whatever.

My own opinion, is that LET should remain a special operator with
backwards-compatibility, but extend the syntax to handle both MVs and
destructuring.  It is great the the CAR of each binding is an atom,
because now the language designers will be able to provide new meaning
to lists in the CAR position, i.e.:

(let* ((x 3)
       (y 4)
       ((q r) (truncate x y)))
  <body>)

or something along these lines.  Personally, I'd like LET handle both
MVs and destructuring.  If they decided to pick only one of those two,
I'd imagine they'd pick destructuring, though everyone seems to want
MVs.  You can get the list back with MULTIPLE-VALUE-LIST anyway.  I'm
guessing that b/c that's what happened with LOOP (AFAIK, LOOP doesn't
easily handle MV bindings in the prologue).

The reason for making the new LET backwards compatible is to prevent
Lisp from adding another operator; plus, if they did, and it worked
out nicely, then it would render so several current operators/macros
obsolete, like LET, LET*, and MULTIPLE-VALUE-BIND.  Better to just
make LET more general and do away with MULTIPLE-VALUE-BIND.  They've
already de-necessitated MULTIPLE-VALUE-SETQ with (SETF VALUES).  

dave



Sun, 03 Feb 2002 03:00:00 GMT  
 multiple-value binding let and let*

| (let* ((x 3)
|        (y 4)
|        ((q r) (truncate x y)))
|   <body>)

  I prefer

(let ((x 3) (y 4) q r)
  (setf (values q r) (truncate x y)))

  although I can see the value of these binding forms, we should not forget
  that DESTRUCTURING-BIND and MULTIPLE-VALUE-BIND go through a lot more
  work than they appear to be and that it's a lot harder to specify exactly
  how these things are supposed to interact with missing values than one
  might believe from watching the trivial cases.

  I note in passing that the generated machine code for the following two
  forms are identical in Allegro CL 5.0.1 for all the machines I have
  tested it on.  (Duane?)

(let (a b) (setf (values a b) (truncate pi 3)) (list a b))
(multiple-value-bind (a b) (truncate pi 3) (list a b))

| Personally, I'd like LET handle both MVs and destructuring.

  I think MULTIPLE-VALUE-BIND and DESTRUCTURING-BIND abuse indentation and
  clutter up the binding/setting distinction, and I see no reason why we
  should now fix that non-problem by cluttering up the meaning of binding
  so much we don't know what we're getting, anymore.

  when I use MULTIPLE-VALUE-BIND and DESTRUCTURING-BIND, it is around short
  pieces of code because I frequently need to rearrange the returned values
  before using them as arguments for another function call, or even to
  avoid the overhead of MULTIPLE-VALUE-CALL, which I would much prefer to
  be implemented at least as efficiently as APPLY (and perhaps with the
  same underlying machinery, even possibly exposed as an argument sequence).

#:Erik
--
  (defun pringles (chips)
    (loop (pop chips)))



Sun, 03 Feb 2002 03:00:00 GMT  
 multiple-value binding let and let*

Quote:

> My own opinion, is that LET should remain a special operator with
> backwards-compatibility, but extend the syntax to handle both MVs and
> destructuring.  It is great the the CAR of each binding is an atom,
> because now the language designers will be able to provide new meaning
> to lists in the CAR position, i.e.:

That's exactly what I was trying to argue against.  Special operators
should *not* be vast complex things partly defined in terms of other
macros like this proposed LET, they should be primitive, simple things
like QUOTE.

If LET is going to get big & hairy then it should lose its status as a
special operator. Assuming the reasons for LET being special in the
first place are still valid, then a new special operator, (`SUBLET')
needs to be added, which does what LET does now.

(Or the new hairy operator could be called something else, like BIND,
which leaves the base language undisturbed, but some people seem
allergic to that.)

--tim



Sun, 03 Feb 2002 03:00:00 GMT  
 multiple-value binding let and let*

  [...]
  > If LET is going to get big & hairy then it should lose its status as a
  > special operator. Assuming the reasons for LET being special in the
  > first place are still valid, then a new special operator, (`SUBLET')
  > needs to be added, which does what LET does now.
  >
  > (Or the new hairy operator could be called something else, like BIND,
  > which leaves the base language undisturbed, but some people seem
  > allergic to that.)

I'm not; I agree that LET should be left as it is.  If we are to have a
new and improved Swiss army knife of a binding construct (I don't
know if it is really such a good idea), the proper approach I think
is the one taken by LOOP.  None of the other iteration constructs in
Lisp were disturbed; for some time people used it as an extension
to the language, and after experience with it was accumulated, it
was adopted as part of the standard.  So let it be with binding.
After all, an omnipotent binding construct is a matter of macrology;
then, if it is useful, people will start using it and after some time,
perhaps, there will be enough voices saying `we want BIND into the
language, it is indispensable.'

My 2e-2.

Vassil Nikolov

For more: http://www.poboxes.com/vnikolov
  Abaci lignei --- programmatici ferrei.

 Sent via Deja.com http://www.deja.com/
 Share what you know. Learn what you don't.



Sun, 03 Feb 2002 03:00:00 GMT  
 multiple-value binding let and let*

Quote:

>   I note in passing that the generated machine code for the following two
>   forms are identical in Allegro CL 5.0.1 for all the machines I have
>   tested it on.  (Duane?)

> (let (a b) (setf (values a b) (truncate pi 3)) (list a b))
> (multiple-value-bind (a b) (truncate pi 3) (list a b))

Yes.  One internal tool I use to figure out what the compiler is
doing is excl::compiler-walk.  It doesn't always tell the truth,
(thus its obscurity) because it can't always emulate the precise
environment that the compiler will actually see.  And in this case,
it is not quite right, but with a little insider help I can get it
close enough:

USER(3): (let ((comp::.for-value. 'comp::multiple))
           (excl::compiler-walk
             '(let (a b) (setf (values a b) (truncate pi 3)) (list a b))))
(LET (A B)
  (LET* ()
    (MULTIPLE-VALUE-BIND (#:G43 #:G44)
        (TRUNCATE 3.141592653589793d0 3)
      (LET () (SETQ A #:G43) (SETQ B #:G44) (VALUES #:G43 #:G44))))
  (EXCL::.PRIMCALL 'SYSTEM::QLIST2 A B))
USER(4): (let ((comp::.for-value. 'comp::multiple))
           (excl::compiler-walk
             '(multiple-value-bind (a b) (truncate pi 3) (list a b))))
(MULTIPLE-VALUE-BIND (A B)
    (TRUNCATE 3.141592653589793d0 3)
  (EXCL::.PRIMCALL 'SYSTEM::QLIST2 A B))
USER(5):

If you try this without the magic in front the walker gets the wrong
idea about where the results are going and so tries to use the one-return
version of truncate.  The awkwardness of these tools is one reason why
I am working on compiler-environments to provide access to the compilation
and macroexpasion process.

--
Duane Rettig          Franz Inc.            http://www.franz.com/ (www)
1995 University Ave Suite 275  Berkeley, CA 94704



Sun, 03 Feb 2002 03:00:00 GMT  
 multiple-value binding let and let*

Quote:

> (let ((x 3) (y 4) q r)
>   (setf (values q r) (truncate x y)))

The problem with this is that you can't put reasonable declarations
in.  You'd like to be able to say:

    (let (a b)
      (declare (type good-type a b))
      (setf (values a b) ...))

But you can't because A and B aren't always of GOOD-TYPE.  You could
wrap a LOCALLY around the inner bit, but that's kind of obviously a
hack.  At least one compiler (CMUCL) cares about these things quite a
lot.  Of course for your case it's easy because you can just invent
cheap initial values, but if the values are large objects there may be
no easy way to get useful initial values.  

At least in the case of multidimensional arrays, type declarations for
large objects really can help, because a suitable declaration with
information about bounds can help compilers generate much better
indexing code.

(The SERIES package has exactly this problem, which is why I know
about it...)

--tim



Sun, 03 Feb 2002 03:00:00 GMT  
 multiple-value binding let and let*

| The problem with this is that you can't put reasonable declarations in.
| You'd like to be able to say:
|
|     (let (a b)
|       (declare (type good-type a b))
|       (setf (values a b) ...))
|
| But you can't because A and B aren't always of GOOD-TYPE.

  I would insist that they are, and that a compiler that barfs on this is
  missing the point and is just being too {*filter*}about its type checking.  if
  there are no references to a local binding prior to its first being set,
  who cares that it has to be NIL if ever there WERE a reference to it?
  the only way that could happen is if you change the code and recompile,
  and then it should barf.  note that the language specification on this
  point may be interpreted to death to support both your point and mine.

#:Erik
--
  (defun pringles (chips)
    (loop (pop chips)))



Mon, 04 Feb 2002 03:00:00 GMT  
 multiple-value binding let and let*

Quote:

>   I would insist that they are, and that a compiler that barfs on this is
>   missing the point and is just being too {*filter*}about its type
>   checking.  

I think that's kind of reasonable, but ...

Quote:
>  note that the language specification on this
>   point may be interpreted to death to support both your point and mine.

it seems to me that the standard is reasonably clear, in particular
the `Declaration TYPE' entry in 3.8 says:

   At the moment the scope of the declaration is entered, the
   consequences are undefined if the value of the declared variable is
   not of the declared type.

And I think that's reasonably clear.  But there may be other things
which lead to other interpretations (my experience of this is just
based on talking to CMUCL people when I discovered that CMUCL took
this interpretation).

--tim



Mon, 04 Feb 2002 03:00:00 GMT  
 multiple-value binding let and let*

|    At the moment the scope of the declaration is entered, the
|    consequences are undefined if the value of the declared variable is
|    not of the declared type.
|
| And I think that's reasonably clear.

  well, it sort of depends on how you interpret "the value of a variable".
  I would argue that if a variable isn't actually accessed, neither does it
  have a value.  I'm aware that the specification isn't fully with me on
  this one, but in my view, there's a distinction between A and B in this
  respect in the form (LET ((A NIL) B) ...), if for no other reason than
  that it communicates a different intent.

  in any case, when the behavior is undefined for value references, and
  there aren't any references, no undefined behavior will occur, right?

#:Erik
--
  (defun pringles (chips)
    (loop (pop chips)))



Mon, 04 Feb 2002 03:00:00 GMT  
 multiple-value binding let and let*

 About (let (a b) (declare (something-non-nil a b)) (setf (values a b) ...

Quote:


> |    At the moment the scope of the declaration is entered, the
> |    consequences are undefined if the value of the declared variable is
> |    not of the declared type.
> |
> | And I think that's reasonably clear.

 [ ... ]

Quote:
>   in any case, when the behavior is undefined for value references, and
>   there aren't any references, no undefined behavior will occur, right?

I may want to talk later about some of the specific compiler issues
brought up in this thread about scope, liveness, and trees falling
in the desert ...  but for now I'd like to address an issue that this
conversation triggers in me that we don't talk about very much.  Perhaps
that is because we don't have a name for it, and so I took the liberty
of coining a phrase "implementationally portable", to which I modified
the subject line.  If anyone has a better name for it, I'm open to
suggestions.  Hopefully its name defines itself relatively obviously, at
least after my discussion below.

Tim is entirely correct that this is nonconformant code, and thus should
not be done in conforming, or portable, code.  However, Erik has a point
of practical significance, and that is that practice does not always
include strict conformance.  Specifically, the "is undefined" is not the
same as "is an error", and means that the vendor/implementor of the lisp
is free to assign defined semantics, either by default or in a public
interface, as to what might happen.  Thus, as long as the user stays within
the vendor's prescribed interface, the code is portable, at least within
the particular lisp or lisps for which the interface is prescribed.

Obviously, implementational portability is not covered in the spec; it
is never the duty of any spec to specify the unspecified :-)  But I
believe that it is a useful concept, and may deserve some attention.

I won't have time to expand on this much, but here are some thoughts:

 1. If every vendor prescribed the same semantics to otherwise nonconforming
code, then it becomes a de-facto standard.  Code written to this standard
can't be considered conformant, but is nonetheless usable.  The downside
is that if a new implementation enters the fray, it has a hard time living
up to the de-facto standards set by the other implementations.  A good example
of this is the PCL implementation, which was the basis for many (but not
all) CLOS implementations.  If a user started counting on functionality
provided by PCL extensions, then the code might be portable to all
PCL-based CLOS implementations, but not to non-PCL based versions.

 2. A widely used technique for establishing implementational portability
is the use of #+/#- to conditionalize.  The plus side is that since the
number of implementations is finite, any source can be made implementationally
portable by porting to all implementations, but the down side is that
new implementations will almost certainly not fit into the #+/#-
portability, without changing the source to add the new version.

 3. Within any one lisp implementation, but not necessarily on the same
architecture, implementational portability is somewhat practical, to the
extent that the vendor/implementor implements the same extensions to all
of their lisps.  To my knowledge, most CL vendors do a fairly good job of
providing implementational portability within their rang of architectures.

The major danger of relying on implementational portability is that
since it is not part of the CL spec, and in fact it may not even be
documented or spec'd explicitly by the implementor, it thus may change
later, breaking your program (and angering you).  Protection from this
requires a good relationship with the supplier of your lisp (or, if you
have source code, a willingness to get in a change it).

--
Duane Rettig          Franz Inc.            http://www.franz.com/ (www)
1995 University Ave Suite 275  Berkeley, CA 94704



Mon, 04 Feb 2002 03:00:00 GMT  
 multiple-value binding let and let*

Quote:

> include strict conformance.  Specifically, the "is undefined" is not the
> same as "is an error", and means that the vendor/implementor of the lisp
> is free to assign defined semantics, either by default or in a public
> interface, as to what might happen.  Thus, as long as the user stays within
> the vendor's prescribed interface, the code is portable, at least within
> the particular lisp or lisps for which the interface is prescribed.

In my haste, I forgot to add a thought to this paragraph:

In other words, "is an error" means "don't do it", whereas "is undefined"
implies "ask your vendor or don't do it", and asking your vendor may either
yield extra functionality, or it may also yield "don't do it".

--
Duane Rettig          Franz Inc.            http://www.franz.com/ (www)
1995 University Ave Suite 275  Berkeley, CA 94704



Mon, 04 Feb 2002 03:00:00 GMT  
 
 [ 41 post ]  Go to page: [1] [2] [3]

 Relevant Pages 

1. let vs. multiple-value-bind

2. (let (flet (multiple-value-bind (etc.))))

3. Distinguish between the usage of Lambda and Let, Let*

4. Distinguish between the usage of Lambda and Let, Let*

5. let and let*

6. (let) and (let*)

7. On let, &aux, and let*

8. LET vs. LET*

9. named-let* ; distinct ids in binding forms

10. LET binding.

11. let bindings

12. Final SRFI 8: RECEIVE: Binding to multiple values

 

 
Powered by phpBB® Forum Software