[racket] lazy letrec-values

From: Stephen Chang (stchang at ccs.neu.edu)
Date: Thu Jul 10 18:41:02 EDT 2014

Actually, this is a bug, because the expression in a single-argument
values call is forced prematurely.

eg, This should not error:

-> (let-values ([(x) (values (error "a"))]) 1)
; a [,bt for context]

Just like this does not error.

-> (let-values ([(x y) (values (error "a") (error "b"))]) 1)
1

Lazy Racket is trying to preserve the (values x) == x from Racket, but
since LR's force is recursive, this is actually impossible without
breaking the semantics like it's doing now.

Luke, thanks for finding this. If you want to submit a pull request, I
will merge. (Just drop the first clause in the case-lambda entirely.)
Maybe some extra tests would be nice as well :) Otherwise if you dont
have time, let me know and I'll do it.

> Beyond the library documentation, does anyone know if there are any discussions or tutorials that go into the do's and don'ts of using #lang lazy ?

There isnt any. You can check out the Barzilay-Clements paper [1] to
learn about the motivation behind LR, but otherwise LR should have
"standard" lazy semantics.

[1]: http://digitalcommons.calpoly.edu/cgi/viewcontent.cgi?article=1047&context=csse_fac

On Thu, Jul 10, 2014 at 1:15 PM, Luke Whittlesey
<luke.whittlesey at gmail.com> wrote:
> Thank you for the in-depth analysis. Very interesting.
>
> Following your reasoning, if I edit lazy.rkt and force `values` to use
> `multiple-values` for the single entry case, the example that was previously
> broken now works. (I just have no idea if this breaks something else in the
> process.)
>
> at lazy.rkt line:223
> replace:
>   (define* ~values
>     (case-lambda [(x) x] [xs (multiple-values xs)]))
>
> with:
>   (define* ~values
>     (case-lambda [(x) (multiple-values (list x))] [xs (multiple-values
> xs)]))
>
>
> I had assumed that a reference to an identifier was delayed, so thanks for
> showing that this is currently not the case.
>
> Beyond the library documentation, does anyone know if there are any
> discussions or tutorials that go into the do's and don'ts of using #lang
> lazy ?
>
> Thanks,
> Luke
>
>
> On Thu, Jul 10, 2014 at 6:24 AM, Matthew Flatt <mflatt at cs.utah.edu> wrote:
>>
>> I'm not sure whether to call it a bug or a limitation of `lazy`.
>>
>> The `lazy` language doesn't delay a reference to an identifier. As a
>> result,
>>
>>  (define x y)
>>  (define y (list 1))
>>  (car x)
>>
>> fails. The case could be made that the right-hand side of the definition
>> of `x` should have been a lazy reference to `y`, but that's not what
>> `lazy` currently does.
>>
>> A problem with the current choice is that it interacts badly with `!`,
>> especially as used by `letrec-values`. The implementation of
>> `letrec-values` forces the right-hand side of a binding using `!` to
>> determine how many values it produces. That works ok when the
>> right-hand side is produced by `values` on more than one argument,
>> because `values` produces a special multiple-values result that leaves
>> its values unforced after `!`. When `values` get one argument, then it
>> just returns the argument.... and that's still ok for something like
>> `(values (list 1 (/ 0)))`, because the `(/ 0)` expression is lazy.
>>
>> In your example, the implicit use of `!` for the right-hand side of the
>> A` binding produces `(! (list a B))`. That `B` is not itself treated as
>> a lazy expression, so forcing the list to be constructed causes `B` to
>> be evaluated early.
>>
>> You can make the variable reference lazy by wrapping it with `~`:
>>
>>   (letrec-values ([(A) (values (list 'a (~ B)))]
>>                   [(B) (values (list 'b A))])
>>     B)
>>
>> Again, I don't know that you should have to do that, but it's how
>> `lazy` is defined at the moment.
>>
>> At Mon, 7 Jul 2014 15:06:26 -0400, Luke Whittlesey wrote:
>> > Hello all,
>> > I've been playing around with creating circular lists (and learning
>> > racket
>> > which has been quite fun), but I'm stumped on why the lazy version of
>> > letrec-values is not producing a promise like the lazy version of letrec
>> > does. With the lazy letrec I can create circular lists, but with the
>> > lazy
>> > letrec-values I get #<undefined>. See the example below.
>> >
>> > ;;;;;;;;;;;;;;;;; example code ;;;;;;;;;;;;;;;;;;;;;;;;;
>> > #lang lazy
>> >
>> > ;; create a circular list using letrec (this works)
>> > (define example-working
>> >   (letrec ([A (list 'a B)]
>> >            [B (list 'b A)])
>> >     B))
>> > (displayln "Working Example:")
>> > (displayln example-working)
>> > (displayln (!! example-working))
>> >
>> > ; Prints...
>> > ;Working Example:
>> > ;(b #<promise:A>)
>> > ;#0=(b (a #0#))
>> >
>> > ;; create a circular list using letrec-values (this is broken)
>> > (define example-broken
>> >   (letrec-values ([(A) (values (list 'a B))]
>> >                   [(B) (values (list 'b A))])
>> >     B))
>> > (displayln "Broken Example:")
>> > (displayln example-broken)
>> > (displayln (!! example-broken))
>> >
>> > ; Prints
>> > ;Broken Example:
>> > ;(b (a #<undefined>))
>> > ;(b (a #<undefined>))
>> > ;;;;;;;;;;;;;;;;; end code ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
>> >
>> > I realize that there are many different ways to generate circular lists,
>> > but why doesn't this work? Am I misunderstanding something or is this a
>> > bug?
>> >
>> > Thanks,
>> > Luke
>> > ____________________
>> >   Racket Users list:
>> >   http://lists.racket-lang.org/users
>
>
>
> ____________________
>   Racket Users list:
>   http://lists.racket-lang.org/users
>

Posted on the users mailing list.