[plt-scheme] about letrec and continuation : which behavior is correct ? and why ?

From: Joe Marshall (jmarshall at alum.mit.edu)
Date: Wed Aug 20 14:27:56 EDT 2008

On Wed, Aug 20, 2008 at 8:20 AM, Abdulaziz Ghuloum
<aghuloum at cs.indiana.edu> wrote:
>
> On Aug 19, 2008, at 2:48 PM, Joe Marshall wrote:
>
>> On Tue, Aug 19, 2008 at 7:44 AM, Abdulaziz Ghuloum
>> <aghuloum at cs.indiana.edu> wrote:
>>>
>>> On Aug 19, 2008, at 7:10 AM, Matthew Flatt wrote:
>>>
>>>> The R5RS/R6RS `letrec' is different, and the result with that
>>>> other `letrec' should be #f:
>>
>> I've always thought this was a wart in the language.
>
> Fine, but how else would letrec be implements in order to provide
> the full generality of letrec (letrec is not fix) and at the same
> time obey the other rules of the imperative, strict, call-by-value
> language known as Scheme?  E.g., how would you evaluate
>
>  (letrec ([x (list (lambda () x))])
>    (eq? x ((car x)))) ; => #t
>
> without a side effect?


The thing that I thought was a wart was that letrec was
*required* to expand as if it were a let followed by a set
even in cases where it could have been accomplished by
a fixed-point computation.  You are forced to be inelegant
even when you don't need to be.


>>> An R6RS implementation may (or may not) raise an &assertion when
>>> the <init> continuation is invoked the second time, right?
>>
>> Ugh.  That's worse than the SET! expansion.
>
> Returning to an <init> continuation may (1) rebind the variable
> as Matthias showed, (2) assign to an already existing binding as
> Matthew showed, or (3) raise an assertion as the report suggests.
>
> Rebinding is not always possible since there is no known general
> letrec transformation that eliminates side effects.  Options 2
> and 3 are always possible for all shapes of letrec, but they're
> ugly as you suggest.  Where does that leave us?

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.  Returning to an <init> continuation
may cause re-assignment or rebinding at the discretion of the
implementation.

Alternatively, the right hand side of the letrec could be restricted.

I rarely use letrec (or internal define) for arbitrary values.  I almost
always use letrec (or internal define) for procedures only.  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.


-- 
~jrm


Posted on the users mailing list.