August 23, 2006
On Tue, 22 Aug 2006 18:39:57 +0100, Tom S wrote:

> Walter, if you *really* want to keep lazy expression evaluation in its current form, then please *at least* don't force everyone to use it.

Amen to that!

> Adopting delegate function parameters for lazy expressions is a mistake.

Well ... its the implicit conversion of expressions to delegates that concerns me. I can see that most of the time it should be harmless, but it still opens the window of opportunity for seriously long debugging sessions.

> At least consider adding a new keyword, e.g. 'lazy' or 'expression', please. The syntax could look like:
> 
> void foo(int expression() foo) {
> }
> 
> or
> 
> void foo(lazy int foo) {
> }

or ...

  void foo( delegate int () foo) {}
  void foo( expression int () foo) {}


> or just some other variant. Delegates would then be delegates and wouldn't accept lazy expressions. But lazy expressions could accept delegates.
> 
> The above approach has three significant advantages over the current state of things:
> 
> 1. When the coder doesn't intend to use lazy expression evaluation, his/her functions that take delegates as params won't silently accept lazily evaluated expressions

A big plus, in the manner of "least surprise".

> 2. Less existing code will be broken or the changes will be easier to fix - renaming the keyword to some other variable name compared to the nontrivial fixes that have to be done now.

This also enhances understanding when reading someone else's code. It allows better understanding of the coder's intentions.

> 3. In future, the 'expression' or 'lazy' types might provide meta-data allowing advanced metaprogramming techniques

Hmmm ... interesting.


-- 
Derek
(skype: derek.j.parnell)
Melbourne, Australia
"Down with mediocrity!"
23/08/2006 2:30:50 PM
August 23, 2006
Tom S wrote:
> Walter, if you *really* want to keep lazy expression evaluation in its current form, then please *at least* don't force everyone to use it.
> 
> Adopting delegate function parameters for lazy expressions is a mistake. At least consider adding a new keyword, e.g. 'lazy' or 'expression', please. The syntax could look like:
> 
> void foo(int expression() foo) {
> }
> 
> or
> 
> void foo(lazy int foo) {
> }
> 
> or just some other variant. Delegates would then be delegates and wouldn't accept lazy expressions. But lazy expressions could accept delegates.
> 
> 
> The above approach has three significant advantages over the current state of things:
> 
> 1. When the coder doesn't intend to use lazy expression evaluation, his/her functions that take delegates as params won't silently accept lazily evaluated expressions
> 2. Less existing code will be broken or the changes will be easier to fix - renaming the keyword to some other variable name compared to the nontrivial fixes that have to be done now.
> 3. In future, the 'expression' or 'lazy' types might provide meta-data allowing advanced metaprogramming techniques
> 
> The third point is important because lazy expressions in their current form simply can't replace expression templates. Even by looking at the first resource available on google...
> 
> http://osl.iu.edu/~tveldhui/papers/Expression-Templates/exprtmpl.html
> 
> ... it's clear that one of the most important points behind expression templates is the ability to deconstruct expressions and basing on their internal structure, generate highly optimized code. At least that's what game developers use to make their linear algebra libraries as fast as possible.
> This is only possible by knowing the structure of the expression and applying localized and specialized optimizations.
> 
> The 'expression' or 'lazy' types might work in a similar way. They would allow the programmer to inspect parse trees at compile-time and generate specially tailored code.
> 
> Currently the only performance advantage that can be accomplished with lazy expression evaluation is when the compiler determines the delegate might be inlined, which currently doesn't happen at all.
> 
> 
> -- 
> Tomasz Stachowiak

I agree, there should be some difference between a delegate and a lazy expression.  Besides fixing an overload ambiguity, this would leave the door open to extensions to lazy expressions that won't necessarily make sense for delegates.

The reason people tout LISP (not that I have used it more than I absolutely had to) as having power that other languages do not is not the lazy evaluation of expressions but the ability to decompose, examine and alter the expression tree using the same code as for any other LISP data structure (since they're all the same anyway).

The reason people think Expression templates in C++ are so impressive is not lazy evaluation - it's compile-time inline insertion of user code into library functions.  So when I write math::integrate(x*x + 2*x + 1, x, 0, 3) I know that my quadratic expression is inserted directly into the inner-most loop of the integral function and incurs no overhead per iteration.

One of C# 3.0's big new features is Expression templates, which is much more than syntactic sugar for anonymous delegates (which it already has and is improving).  The point is that an expression template is not compiled into code but into a data structure that can be explored at run-time.  Thus one can write OO-style C# code that gets turned directly into an efficient SQL query.

Having said all that, the syntax does open some functional-programming doors to D.  I knocked this up while experimenting.  Note the need to pass in the expression argument as a separate parameter - something
a => a * 2 could fix (or some other syntax).

http://www.members.iinet.net.au/~melkesjokolade/functional.d

Andy
August 23, 2006
Andy Knowles wrote:

> http://www.members.iinet.net.au/~melkesjokolade/functional.d
> 
> Andy

I have to ask ... why is your username (melkesjokolade) on this site the norwegian word for milk chocolate? :D

-- 
Lars Ivar Igesund
blog at http://larsivi.net
DSource & #D: larsivi
August 23, 2006
Lars Ivar Igesund wrote:
> Andy Knowles wrote:
> 
>> http://www.members.iinet.net.au/~melkesjokolade/functional.d
>>
>> Andy
> 
> I have to ask ... why is your username (melkesjokolade) on this site the
> norwegian word for milk chocolate? :D
> 

lol :P

my gf is norwegian and we needed a username that was valid for us both and guaranteed not to be taken.  melkesjokolade came to mind, and we both thought it would be suitably confusing to anyone who noticed :D

Andy
August 23, 2006
Andy, is there a particular reason why you don't use the array concatenation operator in your select ( and others ) function(s) ?
Like this one :
# ArrayT [] select ( ArrayT ) ( ArrayT [] array, bool delegate ( ArrayT ) predicate )
# {
#     ArrayT [] result ;
#
#     foreach ( item ; array )
#         if ( predicate ( item ))
#             result ~= item ;
#
#     return result ;
# }


Andy Knowles a écrit :
> Tom S wrote:
>> Walter, if you *really* want to keep lazy expression evaluation in its current form, then please *at least* don't force everyone to use it.
>>
>> Adopting delegate function parameters for lazy expressions is a mistake. At least consider adding a new keyword, e.g. 'lazy' or 'expression', please. The syntax could look like:
>>
>> void foo(int expression() foo) {
>> }
>>
>> or
>>
>> void foo(lazy int foo) {
>> }
>>
>> or just some other variant. Delegates would then be delegates and wouldn't accept lazy expressions. But lazy expressions could accept delegates.
>>
>>
>> The above approach has three significant advantages over the current state of things:
>>
>> 1. When the coder doesn't intend to use lazy expression evaluation, his/her functions that take delegates as params won't silently accept lazily evaluated expressions
>> 2. Less existing code will be broken or the changes will be easier to fix - renaming the keyword to some other variable name compared to the nontrivial fixes that have to be done now.
>> 3. In future, the 'expression' or 'lazy' types might provide meta-data allowing advanced metaprogramming techniques
>>
>> The third point is important because lazy expressions in their current form simply can't replace expression templates. Even by looking at the first resource available on google...
>>
>> http://osl.iu.edu/~tveldhui/papers/Expression-Templates/exprtmpl.html
>>
>> ... it's clear that one of the most important points behind expression templates is the ability to deconstruct expressions and basing on their internal structure, generate highly optimized code. At least that's what game developers use to make their linear algebra libraries as fast as possible.
>> This is only possible by knowing the structure of the expression and applying localized and specialized optimizations.
>>
>> The 'expression' or 'lazy' types might work in a similar way. They would allow the programmer to inspect parse trees at compile-time and generate specially tailored code.
>>
>> Currently the only performance advantage that can be accomplished with lazy expression evaluation is when the compiler determines the delegate might be inlined, which currently doesn't happen at all.
>>
>>
>> -- 
>> Tomasz Stachowiak
> 
> I agree, there should be some difference between a delegate and a lazy expression.  Besides fixing an overload ambiguity, this would leave the door open to extensions to lazy expressions that won't necessarily make sense for delegates.
> 
> The reason people tout LISP (not that I have used it more than I absolutely had to) as having power that other languages do not is not the lazy evaluation of expressions but the ability to decompose, examine and alter the expression tree using the same code as for any other LISP data structure (since they're all the same anyway).
> 
> The reason people think Expression templates in C++ are so impressive is not lazy evaluation - it's compile-time inline insertion of user code into library functions.  So when I write math::integrate(x*x + 2*x + 1, x, 0, 3) I know that my quadratic expression is inserted directly into the inner-most loop of the integral function and incurs no overhead per iteration.
> 
> One of C# 3.0's big new features is Expression templates, which is much more than syntactic sugar for anonymous delegates (which it already has and is improving).  The point is that an expression template is not compiled into code but into a data structure that can be explored at run-time.  Thus one can write OO-style C# code that gets turned directly into an efficient SQL query.
> 
> Having said all that, the syntax does open some functional-programming doors to D.  I knocked this up while experimenting.  Note the need to pass in the expression argument as a separate parameter - something
> a => a * 2 could fix (or some other syntax).
> 
> http://www.members.iinet.net.au/~melkesjokolade/functional.d
> 
> Andy
August 23, 2006
Rémy J. A. Mouëza wrote:
> Andy, is there a particular reason why you don't use the array concatenation operator in your select ( and others ) function(s) ?
> Like this one :
> # ArrayT [] select ( ArrayT ) ( ArrayT [] array, bool delegate ( ArrayT ) predicate )
> # {
> #     ArrayT [] result ;
> #
> #     foreach ( item ; array )
> #         if ( predicate ( item ))
> #             result ~= item ;
> #
> #     return result ;
> # }

:)

An excellent question!  Frankly, I forgot about it.  While I pay close attention to D's development and these news groups, I rarely get a chance to write D code.

It *might* be better to (over)allocate only once (as I do) rather than realloc the array who knows how many times.  Depends on usage really - wasted space on one hand, wasted cycles on the other.

For the range function, it depends how Walter's realloc code works.  I know it allocates more than it needs to, but I don't know what factor or constant it increase the size by.  Depends again on usage which is the better option.

I should probably just use the concat operator and leave the performance tuning for when it is really needed.

And of course, you can't use a predicate with an argument if you want to use lazy evaluation, even though it makes the code more logical.

Andy
August 24, 2006
Ok, I've thought about it a bit:

	void foo(int x);	// same as before 0.165
	void foo(int delegate() x)	// same as before 0.165

and now:

	void foo(lazy int x);

In other words, 'lazy' is now a parameter storage class. This means that:

	void foo(int x);
	void foo(lazy int x);

cannot be distinguished based on overloading, but:

	void foo(lazy int x);
	void foo(int delegate() x);

can be. The implicit conversion of a value to a delegate returning that value would be removed. The conversion happens always (not implicitly) if the parameter storage class is 'lazy'.
August 24, 2006
On Wed, 23 Aug 2006 22:54:35 -0700, Walter Bright wrote:

> Ok, I've thought about it a bit:
> 
> 	void foo(int x);	// same as before 0.165
> 	void foo(int delegate() x)	// same as before 0.165
> 
> and now:
> 
> 	void foo(lazy int x);
> 
> In other words, 'lazy' is now a parameter storage class. This means that:
> 
> 	void foo(int x);
> 	void foo(lazy int x);
> 
> cannot be distinguished based on overloading, but:
> 
> 	void foo(lazy int x);
> 	void foo(int delegate() x);
> 
> can be. The implicit conversion of a value to a delegate returning that value would be removed. The conversion happens always (not implicitly) if the parameter storage class is 'lazy'.

Thanks for do this Walter. The idea of converting expressions to delegates is a good one, and with these changes proposed here it removes a lot of the 'gotchas' that come with implicit conversions. (uint --> int --> uint, and long --> int --> short are still sore points, BTW).

I also assume that the mutability of lazy parameters is identical to the 'in' parameter storage class. That is, any changes to the actual value passed to the function are never passed back to the caller, but that doesn't stop one modifying data passed as references.

 import std.stdio;
 void foo(lazy char[] x)
 {
    x[0..2] = x[1..2] ~ x[0..1];
    x ~= "k";
    writefln("%s", x); // --> "back";
 }

 void main()
 {
    char[] d = "abc";
    foo( d );
    writefln("%s", d); // --> "bac";

 }


-- 
Derek
(skype: derek.j.parnell)
Melbourne, Australia
"Down with mediocrity!"
24/08/2006 4:45:03 PM
August 24, 2006
Walter Bright wrote:
> Ok, I've thought about it a bit:
> 
>     void foo(int x);    // same as before 0.165
>     void foo(int delegate() x)    // same as before 0.165
> 
> and now:
> 
>     void foo(lazy int x);
> 
> In other words, 'lazy' is now a parameter storage class. This means that:
> 
>     void foo(int x);
>     void foo(lazy int x);
> 
> cannot be distinguished based on overloading, but:
> 
>     void foo(lazy int x);
>     void foo(int delegate() x);
> 
> can be. The implicit conversion of a value to a delegate returning that value would be removed. The conversion happens always (not implicitly) if the parameter storage class is 'lazy'.

I think that is definitely a step in the right direction, and I think it will make people a lot more comfortable.

Some questions:

1) While in void foo(lazy int x) will x be evaluated using x() or just x?  Either one could lead to confusion.

2) What are your thoughts on allowing {2*x} as a shortcut for { return 2*x; }?  This would only apply to delegates of course, but may still be useful in places.

3) Any thoughts on a shortcut syntax for arguments to delegates?  It would be nice to be able to write:

  int[] map(int[] arr, int delegate(int) func);
  int[] b = map(a, (x){ 2*x });

or something similar.  At the moment we must write either:

  int[] map(int[] arr, int delegate(int) func);
  int[] b = map(a, (int x){ return 2*x; });

or:

  int[] map(int[] arr, out int arg, lazy int func);
  int x;
  int[] b = map(a, x, 2*x);

Andy
August 24, 2006
Walter Bright wrote:
> Ok, I've thought about it a bit:
> 
>     void foo(int x);    // same as before 0.165
>     void foo(int delegate() x)    // same as before 0.165
> 
> and now:
> 
>     void foo(lazy int x);
> 
> In other words, 'lazy' is now a parameter storage class. This means that:
> 
>     void foo(int x);
>     void foo(lazy int x);
> 
> cannot be distinguished based on overloading, but:
> 
>     void foo(lazy int x);
>     void foo(int delegate() x);
> 
> can be. The implicit conversion of a value to a delegate returning that value would be removed. The conversion happens always (not implicitly) if the parameter storage class is 'lazy'.

Fantastic!  This will even allow lazy values to be pre-constructed, which is a nice perk.


Sean