| Thread overview | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
June 01, 2007 lazy evaluation | ||||
|---|---|---|---|---|
| ||||
The behaviour of lazy in wrt lazy arguments, and it did not worked like I expected. let's take the following code as an example: -------------------------------------------- import std.stdio; int foo() { static int i = 0; return i++; } void baz(lazy int i) { writefln("baz %d", i()); } void bar(lazy int i) { baz(i); writefln("bar %d", i()); } int main() { bar(foo()); return 0; } -------------------------------------------- (small remark, I regret the very handy __func__ variable from C99 here...) if you compile that code, the output will be : baz 0 bar 1 Whereas I would have expected 0 and ... 0. I know the code I show is well, nasty as the lazy expression has a side effect, but it was just a way for me to test if lazy expressions were memoized (what I really expected) or not. It appears they are not, and well, that's not good. On digitalmars website we can read that lazy can be used to avoid computation. Let's say for logging purposes. Good example: the programmer could have two optional backend for logging, e.g. on stderr and in the syslog. and if he does not uses an intermediate variable to store the lazy epxressions, it will be evaluated two times. Of course doing so is completely defeating the very purpose of lazy. There is another language that has some kind of lazy expression support, not only for function parameters but as a type attribute, python users would say decorators, it's Ocaml. See how it works: -------------------------------------------- $ ocaml Objective Caml version 3.09.2 # let a = ref 0;; val a : int ref = {contents = 0} # let counter () = let result = !a in a := !a + 1; result;; val counter : unit -> int = <fun> # counter();; - : int = 0 # counter ();; - : int = 1 # let laz = lazy (counter ());; val laz : int lazy_t = <lazy> # Lazy.force laz;; - : int = 2 # Lazy.force laz;; - : int = 2 -------------------------------------------- lazy types are supported through the Lazy module, and forcing the evaluation of a lazy type is done through Lazy.force expr rather than expr() like in D. Though, like you can see, once forced, the lazy expression sees its value memoized. I'm wondering: * why lazy types in D don't work like that for lazy parameters, because it's really what makes sense and is the most predictible behaviour ; * also why it's not a generic type attribute either and only used as a function parameter. Not knowing the implementation gory details, I don't know if it makes sense at all anyway. -- ·O· Pierre Habouzit ··O madcoder@debian.org OOO http://www.madism.org | ||||
June 01, 2007 Re: lazy evaluation | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Pierre Habouzit | On Fri, Jun 01, 2007 at 07:35:03PM +0000, Pierre Habouzit wrote: > The behaviour of lazy in wrt lazy arguments, and it did not worked > like I expected. let's take the following code as an example: Wow sorry for that sentence, I meant: I tried the lazy arguments feature, and it did not worked like I expected. let's take the following code as an example: [...] -- ·O· Pierre Habouzit ··O madcoder@debian.org OOO http://www.madism.org | |||
June 01, 2007 Re: lazy evaluation | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Pierre Habouzit | Pierre Habouzit wrote:
> On Fri, Jun 01, 2007 at 07:35:03PM +0000, Pierre Habouzit wrote:
>> The behaviour of lazy in wrt lazy arguments, and it did not worked
>> like I expected. let's take the following code as an example:
>
> Wow sorry for that sentence, I meant:
>
> I tried the lazy arguments feature, and it did not worked like I
> expected. let's take the following code as an example: [...]
This is by design, one example was a do_times that evaluated the lazy expression n times.
| |||
June 01, 2007 Re: lazy evaluation | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Pierre Habouzit | Pierre Habouzit wrote:
> lazy types are supported through the Lazy module, and forcing the
> evaluation of a lazy type is done through Lazy.force expr rather than
> expr() like in D. Though, like you can see, once forced, the lazy
> expression sees its value memoized.
>
> I'm wondering:
> * why lazy types in D don't work like that for lazy parameters,
> because it's really what makes sense and is the most predictible
> behaviour ;
> * also why it's not a generic type attribute either and only used as a
> function parameter. Not knowing the implementation gory details, I
> don't know if it makes sense at all anyway.
>
>
In D lazy parameters are implemented as anonymous delegates. This means that
void baz(lazy int i)
{
writefln("baz %d", i);
}
is transformed to
void baz(int delegate() i)
{
writefln("baz %d", i());
}
where the delegate i references is whatever code you used for that argument. So in your example each time i evaluated you call an anonymous function that calls foo() and returns its return value. If you want the value to stay consistent between evaluations of i use a temporary variable to store the result of it or make it non-lazy.
| |||
June 01, 2007 Re: lazy evaluation | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Tyler Knott | On Fri, Jun 01, 2007 at 03:26:34PM -0500, Tyler Knott wrote: > Pierre Habouzit wrote: > > lazy types are supported through the Lazy module, and forcing the > >evaluation of a lazy type is done through Lazy.force expr rather than expr() like in D. Though, like you can see, once forced, the lazy expression sees its value memoized. > > I'm wondering: > > * why lazy types in D don't work like that for lazy parameters, > > because it's really what makes sense and is the most predictible > > behaviour ; > > * also why it's not a generic type attribute either and only used as a > > function parameter. Not knowing the implementation gory details, I > > don't know if it makes sense at all anyway. > > In D lazy parameters are implemented as anonymous delegates. This means that > > void baz(lazy int i) > { > writefln("baz %d", i); > } > > is transformed to > > void baz(int delegate() i) > { > writefln("baz %d", i()); > } > > where the delegate i references is whatever code you used for that argument. So in your example each time i evaluated you call an anonymous function that calls foo() and returns its return value. If you want the value to stay consistent between evaluations of i use a temporary variable to store the result of it or make it non-lazy. hmm okay, so lazy is just syntaxic sugar then. That's a pity, because I believe it's restrictive, but well... too bad :) -- ·O· Pierre Habouzit ··O madcoder@debian.org OOO http://www.madism.org | |||
June 01, 2007 Re: lazy evaluation | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Pierre Habouzit | Pierre Habouzit wrote: > Whereas I would have expected 0 and ... 0. I know the code I show is > well, nasty as the lazy expression has a side effect, but it was just a > way for me to test if lazy expressions were memoized (what I really > expected) or not. It appears they are not, and well, that's not good. If you want to use the result of the argument more than once you should store that in a local variable: void bar(lazy int i) { int j = i(); baz(j); writefln("bar %d", j); } > * also why it's not a generic type attribute either and only used as a > function parameter. Not knowing the implementation gory details, I > don't know if it makes sense at all anyway. Walter gave us indications that the behavior would change as part of the "const clean-up" currently undergoing. This seams like a good time to share your thoughts and improvements on the behavior of lazy. | |||
June 02, 2007 Re: lazy evaluation | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Pierre Habouzit | On Fri, 01 Jun 2007 20:47:45 +0000, Pierre Habouzit wrote:
> On Fri, Jun 01, 2007 at 03:26:34PM -0500, Tyler Knott wrote:
>> Pierre Habouzit wrote:
>> > lazy types are supported through the Lazy module, and forcing the
>> >evaluation of a lazy type is done through Lazy.force expr rather than expr() like in D. Though, like you can see, once forced, the lazy expression sees its value memoized.
>> > I'm wondering:
>> > * why lazy types in D don't work like that for lazy parameters,
>> > because it's really what makes sense and is the most predictible
>> > behaviour ;
>> > * also why it's not a generic type attribute either and only used as a
>> > function parameter. Not knowing the implementation gory details, I
>> > don't know if it makes sense at all anyway.
>>
>> In D lazy parameters are implemented as anonymous delegates. This means that
>>
>> void baz(lazy int i)
>> {
>> writefln("baz %d", i);
>> }
>>
>> is transformed to
>>
>> void baz(int delegate() i)
>> {
>> writefln("baz %d", i());
>> }
>>
>> where the delegate i references is whatever code you used for that argument. So in your example each time i evaluated you call an anonymous function that calls foo() and returns its return value. If you want the value to stay consistent between evaluations of i use a temporary variable to store the result of it or make it non-lazy.
>
> hmm okay, so lazy is just syntaxic sugar then. That's a pity, because
> I believe it's restrictive, but well... too bad :)
>
well, if you don't want it to re-evaluate on each instance, why not just...
int main()
{
int f = foo();
bar(f);
return 0;
}
| |||
June 04, 2007 Re: lazy evaluation | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Denton Cockburn | On Sat, Jun 02, 2007 at 03:01:44AM +0000, Denton Cockburn wrote: > On Fri, 01 Jun 2007 20:47:45 +0000, Pierre Habouzit wrote: > > On Fri, Jun 01, 2007 at 03:26:34PM -0500, Tyler Knott wrote: > >> In D lazy parameters are implemented as anonymous delegates. This means that > >> > >> void baz(lazy int i) > >> { > >> writefln("baz %d", i); > >> } > >> > >> is transformed to > >> > >> void baz(int delegate() i) > >> { > >> writefln("baz %d", i()); > >> } > >> > >> where the delegate i references is whatever code you used for that argument. So in your example each time i evaluated you call an anonymous function that calls foo() and returns its return value. If you want the value to stay consistent between evaluations of i use a temporary variable to store the result of it or make it non-lazy. > > > > hmm okay, so lazy is just syntaxic sugar then. That's a pity, because > > I believe it's restrictive, but well... too bad :) > > > > well, if you don't want it to re-evaluate on each instance, why not just... > > int main() > { > int f = foo(); > bar(f); > return 0; > } My example is oversimplistic. Though if you are e.g. writing (not so random example) an interpreter of some language, and for one or another reason you don't really want to go in the hassle of bytecode or intermediary language. One way to speed things up is to evaluate your parse tree lazily. Of course, that could be explicitely coded, but it's way easier if the language gives you that gratis. As a general rule, in many complex applications, you often have somehow complex operations (parse, compilation, hashing, whatever) that you do "just in case", because it's easier to suppose this operation has been done unconditionnaly, rather than using everywhere you need the parse/compilation/hash/... available things like: if (foo->did_i_parsed_it()) foo->parse(); use_parse_of_foo(foo->parseresult); Because one day, you'll forget about it. If you have the lazy expressions I'm talking about, then instead of doing the real parse in your program, you just need to write the lazy expression that once evaluated will compute the parse/compilation/whatever. Then you use your foo->parseresult member as a lazy expression, that will be evaluated iff your application really needed it. Though, maybe this don't need to be done with the D lazy construct though, but I didn't came with a satisfying implementation yet. (I mean one that has a light enough syntax for the lazy expression use). -- ·O· Pierre Habouzit ··O madcoder@debian.org OOO http://www.madism.org | |||
June 04, 2007 Re: lazy evaluation | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Pierre Habouzit | Pierre Habouzit wrote: > As a general rule, in many complex applications, you often have > somehow complex operations (parse, compilation, hashing, whatever) that > you do "just in case", because it's easier to suppose this operation has > been done unconditionnaly, rather than using everywhere you need the > parse/compilation/hash/... available things like: > > if (foo->did_i_parsed_it()) > foo->parse(); > use_parse_of_foo(foo->parseresult); > > Because one day, you'll forget about it. If you have the lazy > expressions I'm talking about, then instead of doing the real parse in > your program, you just need to write the lazy expression that once > evaluated will compute the parse/compilation/whatever. > > Then you use your foo->parseresult member as a lazy expression, that > will be evaluated iff your application really needed it. How about something like this: --- class Foo { private bool parsed; private Tree parseresult_; Tree parseresult() { if (!parsed) parse(); return parseresult; } private void parse() { parseresult_ = ...; } } --- Then you can just use foo.parseresult without needing to explicitly re-check every time. Properties are very handy for stuff like this. > Though, maybe this don't need to be done with the D lazy construct > though, but I didn't came with a satisfying implementation yet. (I mean > one that has a light enough syntax for the lazy expression use). I think something like the above code combined with passing foo.parseresult as a lazy parameter might do pretty much what you're looking for. | |||
June 04, 2007 Re: lazy evaluation | ||||
|---|---|---|---|---|
| ||||
Posted in reply to Frits van Bommel | Frits van Bommel wrote
> How about something like this:
You describe a generic pattern for memoizing the obvious result of calls; obvious in the sense of forgetting about side effects.
The question Pierre's post rises: is there a way to implement this pattern in D in a way, which is easy to capture?
As it seems there is currently no such way.
If my remark above is true and if it is feasable, then D is in the need of a further paradigm, which at least allows for defining a `memo' qualifier for parameters, where `memo' has the obvious property of evaluating the parameter at most once lazily for each possible parameter value.
As it seems such paradigm is infeasable in general because it must take care of parameters that are itself function calls with lazy parameters. Those calls are not distinguishable before hand even for only one instance, because each of the calls look the same. But because they are evaluated lazily without memoizing the obvious results may vary for consecutive calls.
The question now is: for what kinds of parameters is memoizing allowed? Besides that: my former question in what cases general memoizing makes an algorithm indeed more efficient, stays unanswered.
-manfred
| |||
Copyright © 1999-2021 by the D Language Foundation
Permalink
Reply