[plt-scheme] Re: to define, or to let

From: Bill Richter (richter at math.northwestern.edu)
Date: Thu Apr 22 19:18:44 EDT 2004

   Joe Marshall <jrm at ccs.neu.edu> responds to me:

   > A procedure call (foo goo) where the side-effects of foo affect
   > goo.  In R5RS Scheme, that's a bug, because you don't know the
   > eval order.
   > In Mzscheme, that's "formerly-buggy", because Mzscheme specifies
   > left->right evaluation.  We know that foo is gonna get evaluated
   > first, and so we can count on that, and know what the effect on
   > goo's evaluation is gonna be.

   There is a subtle flaw in your logic here.  

I don't think so, Joe.  I think it's a subtle communication problem:

   If you *know* that foo will have an effect on the evaluation of goo
   in the second example, you should also *know* that in the first
   example.  Therefore, you *wouldn't* write the first example in that
   way, you'd write something more like

     (let* ((proc (foo))
	    (arg  (goo)))
	(proc arg))

Good!  And as I said, it would be a bug to write (foo goo), in R5RS
Scheme, where we don't know the eval order.  But didn't you mean

     (let* ((proc foo)
	    (arg  goo))
	(proc arg))

as an alternative to (foo goo)?  Your code is an alternative, as you
write below, to ((foo) (goo)).  Let's puzzle this out: For me, foo was
gonna return a procedure.  The obvious way to do this is of course 
(define (foo goo) .... )
But for you, hmm..  foo will return a lambda expression, so (foo)
which will then return a procedure?  Sounds like 
(define foo '(lambda (goo) .... ))

   > Right, but if they change the language, they change some bugs.
   > My (foo goo) is always buggy in R5RS Scheme, but it's not a bug
   > in Mzscheme if we're deliberately taking advantage of a feature.

   Again, there is a flaw in the argument.  Since 

     (let* ((proc (foo))
	    (arg  (goo)))
	(proc arg))

   is always correct no matter what the order of evaluation is, 


   and since it clearly notifies the casual observer that (foo) *must*
   be evaluated before (goo), there is no advantage to be gained by
   deliberately writing:

     ((foo) (goo))

   A `feature' that does not confer an `advantage' is not worthy of the

Maybe you're right, Joe.  But that's not a bugginess issue, which is
what I was writing about here. That's a question of whether it's good
coding to take advantage of the left->right eval order feature.

   > Bradd (I think) wrote:
   >    Sequentiality (or lack of it) is an important design decision. 

   To tell the truth, the primary reason that order of evaluation is
   not specified is because MIT did it right to left and Indiana did
   it left to right and there was no compelling argument that could
   sway either side.

Thanks.  I'm compelled by the Standard Reduction theorem of LC, which
says reducing the left-most redex always returns the beta-nf, if it
exists.  To me, you're driving home the point that McCarthy didn't
know LC or LC_v, and/or wasn't particularly concerned with it.

   > Yeah, if we don't know what we're doing!  

   That is normally the case, is it not?

Uh, we normally don't know what we're doing?   Hmm, seems like a
pessimistic view of the human race.  Perhaps you're right :D

   > Knowing about left->right eval, I don't see the difference between
   > (foo goo)
   > and 
   > (let ([eval-function-1st foo])
   >   (let ([eval-argument-2nd goo])
   >     (eval-function-1st eval-argument-2nd)))
   > I mean, the 2nd is driving home the point that we really want to
   > evaluate foo first.   

   That is crucial difference!  Yes, the resulting actions of the
   computer might be identical, but the impression that it has on the
   next person reading the code is quite different.

To R5RS Schemers, yes!  To folks who have been thoroughly ingrained in
Mzscheme's l->r order, maybe not.  Right now, there may not be any
such folks.  See, my subjunctive here was confusing.  

   > But in Mzscheme, you don't have to, and it's "the same" as 
   > (foo goo).

   It may be the same to the computer, but it is *not* the same to me.

I was asking Anton if life might be better if C, C++ & Scheme had from
the beginning mandated l->r eval order.  Because then we flat out
wouldn't have the category of order of eval bugs!!!  But we might have
other problems.  Can you give my thought experiment a try?  

Bradd said something sensible: It's good to have ambiguous eval order,
because it flushes out bugs.  Because in your "normal case", our best
shot at knowing that in (foo goo), that foo & goo's evaluation effects
each other, is to run the program on different interpreters multiple
times & realize we're getting different answers :D


   Incidentally, I'd prefer it if the function position were 
   evaluated *last*.  When I'm developing code, I often write function
   calls to functions that do not yet exist:

     (define (frob my-object)
	 (lambda ()
	   (display "debug:  about to frob")
	   (frob-kernel (prepare-object my-object)))))

     (define (prepare-object object)
       (if (and (pair? object)
		(number? (cdr object)))
	   (cons (car object) (1+ (cdr object)))
	   (error "Bad object")))

   Now I want to test my code:
    (frob (make-some-object))

   > Error:  undefined variable FROB-KERNEL

   Well, I *know* that.  I haven't written it yet.  I wanted to see if
   the argument to FROB-KERNEL was properly prepared.  If the function
   position were evaluated last, I'd have gotten through the call to
   prepare-object.  So now I have to write a stub:

   (define (frob-kernel . args) 
     (display args) 
     (error "Time to write frob-kernel"))

   But I absolutely *hate* doing this.  It reminds me too much of
   those anal languages where everything has to be written and type
   checked before you can test any part of the program.

   With MIT-Scheme's right->left order of evaluation, I can enter the
   debugger just before the call to FROB-KERNEL, look at the arguments
   to ensure they are what I expect, and without restarting from
   scratch define the frob-kernel function and restart the call.

OK, cool, a real-life coder's advantage to right->left.  I decline to
make snotty remarks about data & functions being the "same" in Lisp :D

Anyway, Joe, what about my question: What does HtDP say about mutation
& order of eval issues?  Is there something deficient about HtDP's
approach?  Doesn't handle enough real-world mutation complexities?

Posted on the users mailing list.