Jump to page: 1 2 3
Thread overview
lazy redux
Dec 06, 2009
Tim Matthews
Dec 06, 2009
Michel Fortin
Dec 07, 2009
Michal Minich
Dec 06, 2009
Pelle Månsson
Dec 06, 2009
bearophile
Dec 06, 2009
ZY.Zhou
Dec 07, 2009
Michal Minich
Dec 07, 2009
bearophile
Dec 07, 2009
Michal Minich
Dec 07, 2009
Denis Koroskin
Dec 07, 2009
Michal Minich
Dec 07, 2009
Denis Koroskin
Dec 07, 2009
klickverbot
a modest semicolon thought (was: Re: lazy redux)
Dec 07, 2009
Nick Sabalausky
Dec 07, 2009
retard
Dec 07, 2009
Leandro Lucarella
Dec 07, 2009
Leandro Lucarella
Dec 07, 2009
retard
Dec 07, 2009
Pelle Månsson
Dec 08, 2009
Lutger
Dec 08, 2009
retard
Dec 08, 2009
Michal Minich
December 06, 2009
Should we sack lazy? I'd like it to have a reasonable replacement. Ideas are welcome!

Andrei
December 06, 2009
Andrei Alexandrescu wrote:
> Should we sack lazy? I'd like it to have a reasonable replacement. Ideas are welcome!
> 
> Andrei

According to the doc page: http://www.digitalmars.com/d/2.0/lazy-evaluation.html
you (and/or Tomasz Stachowiak) were the one who suggested it in the first place. How'd it go for you? :)

In haskell land they love lazy evaluation. Lazy evaluation can have many advantages but depending on the situation can be wrote using alternates that d provides:

mixins -- allows turning a string into expression at a later time
delegates -- only called when used explicitly
logical ('&&' and '||') operators already only evaluate what is needed

If it stays though I would prefer a different syntax so where we already have:

char[] delegate() dg
char[] function() fp

then something like this would be more consistent and intuitive:

char[] expression exp

rather than the current:

lazy char[] exp


December 06, 2009
Andrei Alexandrescu wrote:
> Should we sack lazy? I'd like it to have a reasonable replacement. Ideas are welcome!
> 
> Andrei
I think they are broken as they are not really lazy, but just convenient syntax for passing delegates.

In my mind, a lazy parameter should evaluate just once, and save that value. In case of further usage, it should use the saved value instead.

This is actually how I thought they worked until I saw Walter's example with writef(x++).
December 06, 2009
Andrei Alexandrescu:

> Should we sack lazy? I'd like it to have a reasonable replacement. Ideas are welcome!

I am not yet able to suggest you a replacement.
This is a small post I have recently read about limits and problems of laziness in Scala, that looks similar to laziness in D:
http://pchiusano.blogspot.com/2009/05/optional-laziness-doesnt-quite-cut-it.html

I can also show you two usages of mine of lazy. I have used lazy in D1 for two main purposes:

The first problem come from translating Python code to D. In Python the 'or' operator is lazy, and it returns the first not false object it sees (in Python empty collections are false), few examples:

>>> 0 or 4
4
>>> [] or [1,2] or [3]
[1, 2]
>>> (5,) or [1,2]
(5,)
>>> x = 1
>>> def foo(): global x; x += 1
...
>>> [5] or foo()
[5]
>>> x
1
>>> [] or foo()
>>> x
2

It's easy to implement a n-way version of that, this is a reduced 2-way (not tested):

T1 lazyOr(T1, T2)(T1 x, lazy T2 y) {
    static assert(CastableType!(T1, T2), "...");
    if (boolean(x))
        return x;
    else
    	return y;
}


Another small problem of lazy in D is that the caller doesn't know that an expression will be used in a lazy way. This is sometimes handy, but also is not very explicit, so it can lead to troubles. A simile solution to this is to require the 'lazy' on the calling site too:

auto x = lazyOr(a, lazy b);

I don't like that a lot, but it's more explicit.


I have used lazy to implement a poor's man version of the array comps of Python, that I've now seen I can't live without in D. The starting code that I have worked on was from Henning Hasemann in 2007.

This is one of those versions, I use more complex versions too:

TA[] select(TA, TI, TC, TP)(lazy TA mapper,
                            ref TI iter1, TC items1,
                            lazy TP where) {
    ArrayBuilder!(TA) result;
    auto aux1 = iter1; // save original iteration variable

    static if (IsAA!(TC)) {
        foreach (k, v; items1) {
            iter1 = k;
            if (where())
                result ~= mapper();
        }
    } else {
        foreach (el; items1) {
            iter1 = el;
            if (where())
                result ~= mapper();
        }
    }

    iter1 = aux1; // restore original iteration variable
    return result.toarray;
}


A small usage example:

int i;
select(toString(i), i, [1,2,3,4,5,6,7], i % 2 == 0)
==> ["2", "4", "6"]


The disadvantages of that code are big:
- It looks like magic, mostly because of lazy.
- It needs an already defined loop variable (here 'i').
- Even the LDC compiler is not able to compile that code well, so it's not top efficiency, despite the usage of ArrayBuilder inside it.
- It syntax is quite less readable than the Python version:
[str(i) for i in [1,2,3,4,5,6,7] if i % 2 == 0]
- It can be used to produce an actual array only, it can't be used for lazy generators as in Python:
(str(i) for i in [1,2,3,4,5,6,7] if i % 2 == 0)
- It's not a built-in thing, some D programmers may not understand or like it.


But its advantages are bigger than those big disadvantages:
- For not-performance critical parts of the code it's fast enough. Where the profile tells me that some code is slow it's easy to lower the level of the code.
- I have understood only now the main advantage of array comps. Master chess players are able to play many games at the same time and they are able to memorize the configuration of the pieces on many chessboards. Newbie chess players aren't able to remember so many boards. Experiments have shown that if the pieces are put randomly on the board, then master players are able to remember about as many chess positions as newbies or just a little more. During true games masters are able to memorize several chessboards because they don't memorize the position of each piece, they divide the boards in pieces that have a semantic meaning, and then memorize those few chunks. Such 'chunking' is essential during their play too, they think mostly in terms of those chunks, those gestalts, and often not with the movements of single pieces. This chunking is useful because human brains aren't able to manage more than a handful of separated items when they think (about seven), but such items can be complex, they are chunks. Python list comps allow to cut a piece of a code and think of it as a single chunk, allowing me to program at a higher level and better. This is why Python3 has added two more kinds of comps, for sets and dicts:

a_set = {x for x in xrange(10) if x & 1}
a_dict = {x*x : x for x in xrange(100)}

Bye,
bearophile
December 06, 2009
Andrei Alexandrescu Wrote:
> Should we sack lazy? I'd like it to have a reasonable replacement. Ideas are welcome!

Once I had a bug, it's like:
   foo(step1());
   step2();
I forget foo() uses lazy parameter, so step1 never get called.

Now I prefer to use delegate, I just hope this can compile:
  void foo(int delegate() dg){}
  foo({step1()});

December 06, 2009
Tim Matthews wrote:
> Andrei Alexandrescu wrote:
>> Should we sack lazy? I'd like it to have a reasonable replacement. Ideas are welcome!
>>
>> Andrei
> 
> According to the doc page: http://www.digitalmars.com/d/2.0/lazy-evaluation.html
> you (and/or Tomasz Stachowiak) were the one who suggested it in the first place. How'd it go for you? :)

Well the story is, I suggested something very different, namely automatic conversion of expressions to delegates, i.e.:

void fun(int delegate() dg) { ... }

int a;
fun(a += 5); // works, same as fun({ a += 5; });

I am not sure about how good that idea is, but anyway on top of it Tomasz suggested defining a storage class for that, which (1) takes matters to a completely place, (2) marks a sharp decline in the quality of the feature. I protested the addition of "lazy" very strongly in a subsequent post. Since I was relatively new in the newsgroup and the rant bordered on an ad hominem attack against Tomasz, it earned me a good amount of negative goodwill (and for good reasons). If I remember correctly, the flamewar that ensued in my leaving the newsgroup for a good while. I'm very glad the atmosphere has improved so much since - back then it was often some sort of a turf war.

Anyhow, if we leave the feature as an implicit conversion expression -> delegate or function, I think the whole thing is sound (contingent on probably taking care of a couple of corner cases). The remaining problem is that fun(gun()) does not always evaluate gun(), even though the reader who is unaware or forgot about the conversion thinks otherwise. If we require fun({gun();}), the notation is more awkward but also clarifies to the reader what's happening.

My take is this: since assert() establishes a precedent, I'd hate that to be magic inaccessible to mere mortals. Functions like enforce() and logging frameworks can make good use of the feature.

> In haskell land they love lazy evaluation. Lazy evaluation can have many advantages but depending on the situation can be wrote using alternates that d provides:
> 
> mixins -- allows turning a string into expression at a later time
> delegates -- only called when used explicitly
> logical ('&&' and '||') operators already only evaluate what is needed
> 
> If it stays though I would prefer a different syntax so where we already have:
> 
> char[] delegate() dg
> char[] function() fp
> 
> then something like this would be more consistent and intuitive:
> 
> char[] expression exp
> 
> rather than the current:
> 
> lazy char[] exp

I think defining a new type is a lot of work, even more work than "lazy" itself which is just a storage class.


Andrei
December 06, 2009
On 2009-12-06 10:44:17 -0500, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> said:

> Tim Matthews wrote:
>> Andrei Alexandrescu wrote:
>>> Should we sack lazy? I'd like it to have a reasonable replacement. Ideas are welcome!
>>> 
>>> Andrei
>> 
>> According to the doc page: http://www.digitalmars.com/d/2.0/lazy-evaluation.html
>> you (and/or Tomasz Stachowiak) were the one who suggested it in the first place. How'd it go for you? :)
> 
> Well the story is, I suggested something very different, namely automatic conversion of expressions to delegates, i.e.:
> 
> void fun(int delegate() dg) { ... }
> 
> int a;
> fun(a += 5); // works, same as fun({ a += 5; });
> 
> I am not sure about how good that idea is, but anyway on top of it Tomasz suggested defining a storage class for that, which (1) takes matters to a completely place, (2) marks a sharp decline in the quality of the feature. I protested the addition of "lazy" very strongly in a subsequent post. Since I was relatively new in the newsgroup and the rant bordered on an ad hominem attack against Tomasz, it earned me a good amount of negative goodwill (and for good reasons). If I remember correctly, the flamewar that ensued in my leaving the newsgroup for a good while. I'm very glad the atmosphere has improved so much since - back then it was often some sort of a turf war.
> 
> Anyhow, if we leave the feature as an implicit conversion expression -> delegate or function, I think the whole thing is sound (contingent on probably taking care of a couple of corner cases). The remaining problem is that fun(gun()) does not always evaluate gun(), even though the reader who is unaware or forgot about the conversion thinks otherwise. If we require fun({gun();}), the notation is more awkward but also clarifies to the reader what's happening.
> 
> My take is this: since assert() establishes a precedent, I'd hate that to be magic inaccessible to mere mortals. Functions like enforce() and logging frameworks can make good use of the feature.

That's what I'd do too.

But I think we could improve lazy by splitting it in two. The first would work as it does today, allowing things like enforce(). The second could be less intrusive by having no semantic implications for the caller. The idea is simple: force purity on it. This way, the caller doesn't have to think about whether or not an argument is a lazy one or not. Suggested syntax:

	void logIfFalse(bool condition, pure lazy string message);

	logIfFalse(i == 1, createMessage());

If the expression "createMessage()" is pure, then it'll be evaluated lazily. Otherwise it should be evaluated in advance (like normal parameters) and a delegate returning the result should be given to the function.

This would make it mostly a cross-function manual optimization mechanism. The called function can assume its "pure lazy" delegate is pure and optimize things accordingly. If you have a couple of functions passing themselves a "pure lazy" argument like that, a good optimizer could make sure it is never is evaluated more than once even across function boundaries.

-- 
Michel Fortin
michel.fortin@michelf.com
http://michelf.com/

December 07, 2009
Hello Andrei,

> Should we sack lazy? I'd like it to have a reasonable replacement.
> Ideas are welcome!
> 
> Andrei
> 

there are 3 sides from which to look at lazy function parameter.

1. Usage - being able to send expressions to function is very important for writing clear and nice looking code. I think just by requiring enclosure in curly braces "fun({gun();})" would make this feature quite less appealing and used. This syntactic feature is very pleasing - by whichever feature at definition side it is achieved (macro/expression type), it should stay here.

2. Writing - On the function definition side, I don't see much difference in *writing* "lazy int dg" or "int delegate () dg". The functions that take lazy parameter are not written daily - their usage is much more frequent (enforce, logging). 

One problem I see currently with "lazy" that by specification it can be evaluated zero or more times. For me, "lazy" means zero or one time (compiler should handle this internally). This is not particularly important, because it is probably not so hard for programmer to write correct function - evaluation as many times as needed (which I think is usually 0 or 1 anyway). Just, the name "lazy" does not seems correct to me.

3. Contract - It was mentioned that programmer may expect "foo" to be always called in "bar (foo())". In case bar takes lazy argument, it may not be called. 


We need to have function which takes expression as parameter, while being able to separately express that function takes delegate. This should minimize cases of possible mismatched parameters. If we would replace "lazy" with explicit delegate parameter specification in function declaration, we would lost this possibility, and I think there would be more bugs cased by mismatched parameters.

So I think we should leave "lazy" in, until we have macros. But introduction "{ epx }" as delegate/function literal for functions with no arguments, which implicitly returns result of the expression, seems to me as a good idea.


December 07, 2009
Hello Michel,

> 
> void logIfFalse(bool condition, pure lazy string message);
> 
> logIfFalse(i == 1, createMessage());

I like the idea that of restricting what is passed into function;

void logIfFalse(bool condition, lazy pure nothrow @safe string message);

In wich case, expression passed needs to be checked for these restrictions.

But I would leave the semantics of current lazy as is, or with one adjustment: It would be better if the compiler could make sure it is executed only zero or one time (but I don't think it has much practical advantage).


December 07, 2009
Michal Minich:
> But introduction "{ epx }" as delegate/function literal for functions with no arguments, which implicitly returns result of the expression, seems to me as a good idea.

It's a special case, and special cases help to kill languages. It's not important enough.
But a general shorter syntax for lambdas is possible, like the C# one.
Evaluations lazy arguments only 0 or 1 times sounds like a nice idea.

Bye,
bearophile
« First   ‹ Prev
1 2 3