[plt-scheme] Re: to define, or to let
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,
yes.
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
name.
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)
(call-with-complicated-setup
(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?