[plt-scheme] about letrec and continuation : which behavior is correct ? and why ?
On Wed, Aug 20, 2008 at 12:44 PM, Abdulaziz Ghuloum
<aghuloum at cs.indiana.edu> wrote:
>
> On Aug 20, 2008, at 11:27 AM, Joe Marshall wrote:
>
>> Option 4: An implementation *may* implement letrec as a binding
>> followed by an assignment, or it *may* implement letrec as a
>> fixed point, or it *may* mix the two.
>
> This is precisely what I do in my compiler.
>
>> Returning to an <init> continuation
>> may cause re-assignment or rebinding at the discretion of the
>> implementation.
>
> How is this better than raising an exception? (Note, Ikarus does
> not raise such exceptions, so, it is far less work for me and far
> more efficient for Ikarus to not introduce these checks for programs
> that return more than once to an <init>, but I still don't see that
> choosing to rebind or to assign depending on the implementation's
> choice is better than raising an exception)
It's better than raising an exception because returning to an
<init> continuation doesn't necessarily mean your program is
broken. If your program doesn't try to do anything `funny' with
the bindings, then it shouldn't notice or care if they are re-assigned
or rebound and there is no reason to raise an exception. (In other
words, I could write a program that worked perfectly well on your
compiler and didn't depend on whether things were rebound or
re-assigned, yet it wouldn't work at all on someone else's compiler
because they raise an exception. (But it *would* work if they were
lazy enough not to check.) Or alternatively, someone could perversely
depend on that exception and discover it didn't happen on your
compiler.
>
>> Alternatively, the right hand side of the letrec could be restricted.
>
> No! Any restriction like that invalidates way too many valid and
> perfectly good programs that I do write all the time.
>
>> I rarely use letrec (or internal define) for arbitrary values. I almost
>> always use letrec (or internal define) for procedures only.
>
> I can't say the same. For example, I consider parameters (i.e.,
> the result of calling make-parameter), record accessors, mutators,
> constructors, etc., to be procedures even if they're obtained by
> procedural means (from non-lambda expressions).
>
> This is a simple sequence of definitions:
>
> (define make-adder (lambda (n) (lambda (m) (+ n m))))
> (define add1 (make-adder 1))
>
> Do you never write code like this?
Not often. It may be a habit I picked up from working
in Common Lisp where you don't have letrec (you have
labels, which only allows function binding). It's trivial enough
to write
(let ((add1 (make-adder 1)))
...
(add1 22))
And I find internal defines to be a tiny bit odd. I like the way
they look, but the fact that you have to scan them out is
distasteful.
In my mental model, I see `let' as introducing a binding cell
that contains a value. I see internal define as introducing a
named routine that I can invoke or return, but not as introducing
a `variable' that can be mutated.
>
>> If this
>> were the case, then you'd have to explicitly have a side effect in
>> the code above:
>>
>> (let ((x (list #f)))
>> (set-car! x (lambda () x))
>> (eq? x ((car x))))
>>
>> Which, frankly, is a better way of writing this than the letrec
>> because it is obvious that some side-effecting is going on in
>> order to create magic circular structure such that
>> (eq? x ((car x))) could evaluate to #t.
>
> I agree that this is clearer and that letrec/call-cc tricks are
> good for puzzles only and are never useful in practice. However,
> ruling out all non-lambdas or non-trivial expressions from letrec
> and internal defines is an overkill; especially when the motivation
> for doing so is just to disallow such puzzles.
>
> Aziz,,,
>
--
~jrm