[racket] lazy letrec-values

From: Matthias Felleisen (matthias at ccs.neu.edu)
Date: Thu Jul 10 21:42:41 EDT 2014

There are also Eli's class notes. I don't have a URL handy but I am sure if you google "Eli Barzilay" and "course" you'll find his notes on the various levels of lazy (plus homework assignments :-) -- Matthias






On Jul 10, 2014, at 6:41 PM, Stephen Chang wrote:

> 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
>> 
> ____________________
>  Racket Users list:
>  http://lists.racket-lang.org/users



Posted on the users mailing list.