March 12, 2007
Foo wrote:
> I think the syntax of delegate literal should be simplified, like this:
> int[] squares = numbers.map( int(int x){return x * x;} );
> 
> or ruby-like syntax:
> int[] squares = numbers.map( {int|int x| x * x} );

*ahem* You just didn't simplify _enough_ ;):
---
$ cat test.d
import std.stdio;

R[] map(T,R)(T[] arr, R delegate(T) dg){
    R[] result = new R[arr.length];
    foreach(i, item; arr)
        result[i] = dg(item);
    return result;
}

void main(){
    int[] numbers = [1, 2, 3, 4, 5, 6, 7, 8];
    int[] squares = numbers.map( (int x){return x * x;} );
    writefln(squares);
}
$ dmd -run test.d
[1,4,9,16,25,36,49,64]
---
As you can see, your first example works if you also leave out the return type :).
(See http://www.digitalmars.com/d/expression.html#FunctionLiteral)

I'm not quite sure why you're not allowed to explicitly specify the return type without also specifying "function" or "delegate" first, but it could be there's some syntactic ambiguity that would create which I can't think of right now. Or it could just be that Walter overlooked it, thought it too hard to implement, or simply not worth it for some reason...


[a bit later]
I just thought of a semi-ambiguity. If the return type and the types of any parameters are user-defined, and all parameters (if any) are anonymous and implicitly 'in', the first part looks just like a function call: "foo(bar)" can then be either a call to a function 'foo' with parameter 'bar', or the start of a delegate returning a value of type 'foo' taking an anonymous parameter of type 'bar'.
But since IIRC D already requires an infinite-lookahead parser, the next character (either a '{' or not) should disambiguate, which is why I only called this a semi-ambiguity.
Can anyone think of an actual ambiguity, or think of another reason this is not allowed? Barring fault, oversight or laziness (or perhaps just busy-ness) on Walter's part, of course :P.
March 12, 2007
On Mon, 12 Mar 2007 09:34:39 -0400, Foo <foo@bar.zoo> wrote:

>I think the syntax of delegate literal should be simplified, like this:
>int[] squares = numbers.map( int(int x){return x * x;} );
>
>or ruby-like syntax:
>int[] squares = numbers.map( {int|int x| x * x} );

This works:
int[] squares = numbers.map((int x){return x * x;} );

The return value of the delegate literal is inferred from the return expression type.


March 12, 2007
On Mon, 12 Mar 2007 16:14:09 +0200, Max Samukha <samukha@voliacable.com> wrote:

>The return value of the delegate literal is inferred from the return expression type.
>

Not return value, return type, of course. Frits was faster, anyway.
March 12, 2007
Frits van Bommel wrote:
>
> I just thought of a semi-ambiguity. If the return type and the types of any parameters are user-defined, and all parameters (if any) are anonymous and implicitly 'in', the first part looks just like a function call: "foo(bar)" can then be either a call to a function 'foo' with parameter 'bar', or the start of a delegate returning a value of type 'foo' taking an anonymous parameter of type 'bar'.

It could also look like an object instantiation via static opCall followed by a nested block of code.  Excluding the separating semicolon, of course.

> But since IIRC D already requires an infinite-lookahead parser, the next character (either a '{' or not) should disambiguate, which is why I only called this a semi-ambiguity.

Yup.

> Can anyone think of an actual ambiguity, or think of another reason this is not allowed? Barring fault, oversight or laziness (or perhaps just busy-ness) on Walter's part, of course :P.

I think the presence or lack of a semicolon keeps this from being a true ambiguity.
March 12, 2007
Sean Kelly wrote:
> Frits van Bommel wrote:
>>
>> I just thought of a semi-ambiguity. If the return type and the types of any parameters are user-defined, and all parameters (if any) are anonymous and implicitly 'in', the first part looks just like a function call: "foo(bar)" can then be either a call to a function 'foo' with parameter 'bar', or the start of a delegate returning a value of type 'foo' taking an anonymous parameter of type 'bar'.
> 
> It could also look like an object instantiation via static opCall followed by a nested block of code.  Excluding the separating semicolon, of course.

A static opCall is also a regular function as far as the syntax is concerned, just one that happens to have the same name as an aggregate type. AFAIK the compiler doesn't even generate different code for this (except for the actual function address concerned, of course). A non-static opCall is slightly different here, but should AFAIK generate code equivalent to a that of a delegate call, except the function is a constant instead of stored in memory.

See below regarding the semicolon.

>> But since IIRC D already requires an infinite-lookahead parser, the next character (either a '{' or not) should disambiguate, which is why I only called this a semi-ambiguity.
> 
> Yup.
> 
>> Can anyone think of an actual ambiguity, or think of another reason this is not allowed? Barring fault, oversight or laziness (or perhaps just busy-ness) on Walter's part, of course :P.
> 
> I think the presence or lack of a semicolon keeps this from being a true ambiguity.

No, I think it's the presence of an opening brace, not a semicolon; for instance, it's still a function call if the next token is some sort of binary operator ('+', '*', '/', '.', whatever): in the expression "foo(bar) + 5" the "foo(bar)" must be a function or delegate call of some sort. (Perhaps you failed to consider that functions are allowed to return values? :) )
So the absence of a semicolon doesn't mean it's not a function call, whereas the absence of an opening brace does mean it's not a delegate literal. So the opening brace would be the superior discriminating token, since its presence also unambiguously (AFAICT) indicates a delegate literal.

But as I immediately indicated, I agree that this isn't a real ambiguity (and that it was just the closest thing to one I could come up with :) ). Which I consider to be a good thing, as I'm hoping there isn't any ambiguity and this syntax can therefore be allowed in a future D version.
March 12, 2007
Frits van Bommel wrote:
> Sean Kelly wrote:
>> Frits van Bommel wrote:
>>>
>>> I just thought of a semi-ambiguity. If the return type and the types of any parameters are user-defined, and all parameters (if any) are anonymous and implicitly 'in', the first part looks just like a function call: "foo(bar)" can then be either a call to a function 'foo' with parameter 'bar', or the start of a delegate returning a value of type 'foo' taking an anonymous parameter of type 'bar'.
>>
>> It could also look like an object instantiation via static opCall followed by a nested block of code.  Excluding the separating semicolon, of course.
> 
> A static opCall is also a regular function as far as the syntax is concerned, just one that happens to have the same name as an aggregate type. AFAIK the compiler doesn't even generate different code for this (except for the actual function address concerned, of course). A non-static opCall is slightly different here, but should AFAIK generate code equivalent to a that of a delegate call, except the function is a constant instead of stored in memory.
> 
> See below regarding the semicolon.
> 
>>> But since IIRC D already requires an infinite-lookahead parser, the next character (either a '{' or not) should disambiguate, which is why I only called this a semi-ambiguity.
>>
>> Yup.
>>
>>> Can anyone think of an actual ambiguity, or think of another reason this is not allowed? Barring fault, oversight or laziness (or perhaps just busy-ness) on Walter's part, of course :P.
>>
>> I think the presence or lack of a semicolon keeps this from being a true ambiguity.
> 
> No, I think it's the presence of an opening brace, not a semicolon; for instance, it's still a function call if the next token is some sort of binary operator ('+', '*', '/', '.', whatever): in the expression "foo(bar) + 5" the "foo(bar)" must be a function or delegate call of some sort. (Perhaps you failed to consider that functions are allowed to return values? :) )

No, I was merely thinking of the syntax in general rather than specifically in a function call.  For example, I believe this is legal:

(int x) {printf("%d\n", x); }( 2 );

> So the absence of a semicolon doesn't mean it's not a function call, whereas the absence of an opening brace does mean it's not a delegate literal.

In the context of a function call, I agree.

> But as I immediately indicated, I agree that this isn't a real ambiguity (and that it was just the closest thing to one I could come up with :) ). Which I consider to be a good thing, as I'm hoping there isn't any ambiguity and this syntax can therefore be allowed in a future D version.

Yup :-)


Sean
March 12, 2007
Argument(s) type could be inferred too, as it is currently with foreach.
So

auto res = [1,2,3].map((x){return x+x;}).filter((x, i){return i<2;});

Almost array comprehension.
Another possibility is to do it C# 3.0 way

auto res = [1,2,3].map(x=> x+x).filter(_, i => i<2).sort(a,b => a>b);
Such syntax is consistent with current lazy arguments.


Max Samukha Wrote:

> On Mon, 12 Mar 2007 16:14:09 +0200, Max Samukha <samukha@voliacable.com> wrote:
> 
> >The return value of the delegate literal is inferred from the return expression type.
> >
> 
> Not return value, return type, of course. Frits was faster, anyway.

March 13, 2007

Vassily Gavrilyak wrote:
> Argument(s) type could be inferred too, as it is currently with foreach.
> So
> 
> auto res = [1,2,3].map((x){return x+x;}).filter((x, i){return i<2;});

I wouldn't want to be the one to write the type inference code for that :3

[1,2,3].map(f) works because it's rewritten as map([1,2,3], f), and the type of [1,2,3] and f is known.  But if the type of f is inferred from the call to map, and the call to map needs to know the type of f in order to know its' own type...

*groans*  My brain hurts just trying to think about it >_<

Not saying I wouldn't *love* to have this, just that I wouldn't be at all upset if it never happened :P

> Almost array comprehension.
> Another possibility is to do it C# 3.0 way
> 
> auto res = [1,2,3].map(x=> x+x).filter(_, i => i<2).sort(a,b => a>b);
> Such syntax is consistent with current lazy arguments.

Whoa, now *that* is nasty.  Quick: is foo(a,b=>a>b) a delegate of two arguments or one?  What if a is declared outside the scope; which is it then?

Hell, I couldn't even work out what the "_" was for until I realised it was allowing multiple arguments :P

> 
> Max Samukha Wrote:
> 
>> On Mon, 12 Mar 2007 16:14:09 +0200, Max Samukha <samukha@voliacable.com> wrote:
>>
>>> The return value of the delegate literal is inferred from the return expression type.
>>>
>> Not return value, return type, of course. Frits was faster, anyway.
> 

Frits is a forum-post ninja; beware his stealthy and sudden posts!

	-- Daniel

-- 
Unlike Knuth, I have neither proven or tried the above; it may not even make sense.

v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP  http://hackerkey.com/
March 13, 2007
Strange, for me inferring RETURN type is much more difficult then argument type,
and D already do exactly this.
Arguments types are already declared, in declaration of map
R[] map(T[] arr, R delegate(T) fun);

From here it is clear that there is one argument, and its type is T for array of T's.
But probably some D features will disallow such simple kind of inference.
Inferring type R from above example seems much more complicated -
you need to look at the delegate code and find the return type.

> Whoa, now *that* is nasty.  Quick: is foo(a,b=>a>b) a delegate of two arguments or one?  What if a is declared outside the scope; which is it
Hmm, for me it looks simple :-)
It's a shortcut for delegate (int a, int b){return a>b;}
It's the same as lazy  3+2  is just a shortcut for (void){return 3+2;}, but with parameters.
From expanded version it is clear that a and b are local arguments and normal D rules
of name resolution is applied. (well, I do not know the exact rules, but it seems
as it will be error having the same variable name upper in the scope).
Another similar example of already existing type inference is
foreach(i, v; arr)
Here i and v are inferred from enumerated collection type, and map is actually the same
arr.map((i, v)=>v+v);

Hmm, but you are right with your question, it seems that parenthesis are mandatory
because otherwise it will be impossible to differentiate how many arguments needed.
So the correct version will look like
auto sorted = sort(arr,  (a,b) => a<b );


> *groans*  My brain hurts just trying to think about it >_<
Ouch, I'm sorry for this. You give me a wonderful functools.d and I hurt your brain
in response. Never wanted such thing to happen.
But that are actual examples of how I am currently using functools.d :-).
Many thanks for this lib, it helps me a lot.

And that was actual example of C# 3.0 syntax.
Actually they implemented comprehensions too, but with somewhat strange "SQL-like"
syntax. So my example with such syntax will look something like:
var res = FROM [1,2,3] AS x SELECT x+x WHERE x<2 ORDER BY x;
It's probably more readable but that's totally different language, no spirit of C here.

To conclude - D needs parametrized lazy arguments and more aggressive type inference.
It will cover most syntax sugar issues with array comprehensions and much more,
because syntax will not be restricted.
Example I actually use often:
Given an array of people build an associative array with name as a key;
Person[char[]] peopleByName = people.hashBy((person)=>person.name);


March 14, 2007

Vassily Gavrilyak wrote:
> Strange, for me inferring RETURN type is much more difficult then argument type,
> and D already do exactly this.
> Arguments types are already declared, in declaration of map
> R[] map(T[] arr, R delegate(T) fun);
> 
> From here it is clear that there is one argument, and its type is T for array of T's.
> But probably some D features will disallow such simple kind of inference.
> Inferring type R from above example seems much more complicated -
> you need to look at the delegate code and find the return type.

See, the way I *think* DMD does it is that it goes from the inner-most expression out.  So at the very inner-most level, we've got the delegate.  It starts off by getting the type of the arguments, since we need to know these to make sense of the body.  All good.  We then have "return a>b".  Well, we know a and b, so the result of this is obviously a bool.  We go to check the return type and... it's missing.  Hmm, well, let's just fix it to bool.  Alright--delegate's done.

Now we come to the map call.  We're given an argument that's int[], so T is obviously int.  We're given a delegate of type bool delegate(int,int), so R is obviously bool.  Easy.

If we switch it around, it becomes messier: we need to do the type inference for the call to map BEFORE we work out what the type of the delegate is.  But we can't work out all the types, since we need to know the type of the delegate to get R.  So we end up with the types of map depending on the delegate, and the types on the delegate depending on map.  Where do you start?

The obvious answer to this is to make the whole AST lazily-evaluated, but I like Walter, and I think it's important for people to go outside once in a while :P

>> Whoa, now *that* is nasty.  Quick: is foo(a,b=>a>b) a delegate of two arguments or one?  What if a is declared outside the scope; which is it
> Hmm, for me it looks simple :-)
> It's a shortcut for delegate (int a, int b){return a>b;}
> It's the same as lazy  3+2  is just a shortcut for (void){return 3+2;}, but with parameters.
> From expanded version it is clear that a and b are local arguments and normal D rules
> of name resolution is applied. (well, I do not know the exact rules, but it seems
> as it will be error having the same variable name upper in the scope).
> Another similar example of already existing type inference is
> foreach(i, v; arr)
> Here i and v are inferred from enumerated collection type, and map is actually the same
> arr.map((i, v)=>v+v);
>
> Hmm, but you are right with your question, it seems that parenthesis are mandatory
> because otherwise it will be impossible to differentiate how many arguments needed.
> So the correct version will look like
> auto sorted = sort(arr,  (a,b) => a<b );

I just wanted to point out that the syntax is wildly different from anything else in D.  The advantage of the current syntax, is that it *looks* like some kind of function... it's got the parenthesised argument list and the curly-brace body.

I see "=>" and immediately think of maps (er, the associative-array kind.)

Of course, the above syntax is also much shorter for single expressions.

>> *groans*  My brain hurts just trying to think about it >_<
> Ouch, I'm sorry for this. You give me a wonderful functools.d and I hurt your brain
> in response. Never wanted such thing to happen.
> But that are actual examples of how I am currently using functools.d :-).
> Many thanks for this lib, it helps me a lot.

You're very welcome!  I'm overjoyed to hear that someone is actually using it!

> And that was actual example of C# 3.0 syntax.

I know; I always preferred Nemerle, tho :P  You wanna talk about type-inference, go check that one out!

> Actually they implemented comprehensions too, but with somewhat strange "SQL-like"
> syntax. So my example with such syntax will look something like:
> var res = FROM [1,2,3] AS x SELECT x+x WHERE x<2 ORDER BY x;
> It's probably more readable but that's totally different language, no spirit of C here.

I think that they started developing LINQ because people wanted list comprehensions, but instead of taking their cues from Haskell or Python... they used SQL.  It's debatable as to whether that was a good idea or not; I'll just say that I prefer Haskell/Python style list comprehensions :)

> To conclude - D needs parametrized lazy arguments and more aggressive type inference.
> It will cover most syntax sugar issues with array comprehensions and much more,
> because syntax will not be restricted.
> Example I actually use often:
> Given an array of people build an associative array with name as a key;
> Person[char[]] peopleByName = people.hashBy((person)=>person.name);

With enough semantic analysis, I imagine you could get D to do anything.
  I agree, it *would* be nice to have, and being able to write shorter
lambda functions is always nice.  I wouldn't say it "needs" them,
though: I'd prefer to see more features that really honestly *can't* be
done at the moment added (like compile-time function evaluation or
better compile-time reflection) before incremental improvements like that.

Anyway, that's just my opinion, for what that's worth :P

I'll go now, I think I've started rambling again.

	-- Daniel

-- 
Unlike Knuth, I have neither proven or tried the above; it may not even make sense.

v2sw5+8Yhw5ln4+5pr6OFPma8u6+7Lw4Tm6+7l6+7D i28a2Xs3MSr2e4/6+7t4TNSMb6HTOp5en5g6RAHCP  http://hackerkey.com/