Browse files

add => lambda syntax

  • Loading branch information...
1 parent 8fa99fc commit 675898721c04d0bf155a85abf986eae99c37c0dc @WalterBright WalterBright committed Dec 27, 2011
Showing with 56 additions and 5 deletions.
  1. +5 −0 src/lexer.c
  2. +2 −1 src/lexer.h
  3. +33 −3 src/parse.c
  4. +16 −1 test/runnable/template4.d
@@ -1146,6 +1146,10 @@ void Lexer::scan(Token *t)
t->value = TOKequal; // ==
+ else if (*p == '>')
+ { p++;
+ t->value = TOKgoesto; // =>
+ }
t->value = TOKassign; // =
@@ -3149,6 +3153,7 @@ void Lexer::initKeywords()
Token::tochars[TOKat] = "@";
Token::tochars[TOKpow] = "^^";
Token::tochars[TOKpowass] = "^^=";
+ Token::tochars[TOKgoesto] = "=>";
// For debugging
@@ -34,7 +34,7 @@ struct Module;
= ! ~ @
^^ ^^=
++ --
- . -> : ,
+ . -> : , =>
? && ||
@@ -168,6 +168,7 @@ enum TOK
+ TOKgoesto,
@@ -5105,6 +5105,21 @@ Expression *Parser::parsePrimaryExp()
tempinst->tiargs = parseTemplateArgument();
e = new ScopeExp(loc, tempinst);
+ else if (token.value == TOKgoesto)
+ { // identifier => expression
+ Type *at = new TypeIdentifier(loc, id);
+ Parameter *a = new Parameter(0, at, NULL, NULL);
+ Parameters *arguments = new Parameters();
+ arguments->push(a);
+ TypeFunction *tf = new TypeFunction(arguments, NULL, 0, linkage, 0);
+ FuncLiteralDeclaration *fd = new FuncLiteralDeclaration(loc, 0, tf, TOKdelegate, NULL);
+ check(TOKgoesto);
+ Expression *ae = parseAssignExp();
+ fd->fbody = new ReturnStatement(loc, ae);
+ fd->endloc = loc;
+ e = new FuncExp(loc, fd);
+ break;
+ }
e = new IdentifierExp(loc, id);
@@ -5430,7 +5445,22 @@ Expression *Parser::parsePrimaryExp()
case TOKlparen:
- if (peekPastParen(&token)->value == TOKlcurly)
+ { enum TOK past = peekPastParen(&token)->value;
+ if (past == TOKgoesto)
+ { // (arguments) => expression
+ int varargs;
+ Parameters *arguments = parseParameters(&varargs);
+ TypeFunction *tf = new TypeFunction(arguments, NULL, varargs, linkage, 0);
+ FuncLiteralDeclaration *fd = new FuncLiteralDeclaration(loc, 0, tf, TOKdelegate, NULL);
+ check(TOKgoesto);
+ Expression *ae = parseAssignExp();
+ fd->fbody = new ReturnStatement(loc, ae);
+ fd->endloc = loc;
+ e = new FuncExp(loc, fd);
+ break;
+ }
+ else if (past == TOKlcurly)
{ // (arguments) { statements... }
save = TOKdelegate;
goto case_delegate;
@@ -5441,6 +5471,7 @@ Expression *Parser::parsePrimaryExp()
e->parens = 1;
check(loc, TOKrparen);
+ }
case TOKlbracket:
{ /* Parse array literals and associative array literals:
@@ -5498,7 +5529,6 @@ Expression *Parser::parsePrimaryExp()
Parameters *arguments;
int varargs;
- FuncLiteralDeclaration *fd;
Type *t;
StorageClass stc = 0;
@@ -5525,7 +5555,7 @@ Expression *Parser::parsePrimaryExp()
TypeFunction *tf = new TypeFunction(arguments, t, varargs, linkage, stc);
- fd = new FuncLiteralDeclaration(loc, 0, tf, save, NULL);
+ FuncLiteralDeclaration *fd = new FuncLiteralDeclaration(loc, 0, tf, save, NULL);
e = new FuncExp(loc, fd);
@@ -888,7 +888,7 @@ void test33() {
Composer!(double) comp;
comp += delegate double (double x) { return x/3.0;};
comp += delegate double (double x) { return x*x;};
- comp += delegate double (double x) { return x+1.0;};
+ comp += (double x) => x + 1.0;
writefln("%f", comp(2.0));
// Try function objects
@@ -1022,6 +1022,20 @@ void instantiate4652()
+int bar39(alias dg)(int i)
+ return dg(i);
+void test39()
+ auto i = bar39!(a => a + 1)(3);
+ if (i != 4)
+ assert(0);
int main()
@@ -1062,6 +1076,7 @@ int main()
+ test39();
return 0;

20 comments on commit 6758987


Only took four years!.. no wait, five

But in seriousness, at least this means an end to the silly string mixins.


Finally! Thanks a lot!


The one I was missing after C# for these years. Thanks!


Same here. The other feature I really miss is object initializers: and attributes.

class C {
int a, b;

auto c = new C();
with(c) {
a = 1;
b = 2;

var c = new C() { a = 1, b = 2 };


That works for structs, but for class I get "Error: no constructor for C".
Even if it did work, rearranging the fields, or adding new ones, would give unexpected results. It's a feature I don't dare to use.. Adding a constructor that has the same number and types of parameters, but that doesn't assign in the same order would also compile without a warning, but could change the code completely.

auto c = new C(); with(c) {
a = 1;
b = 2;

Is a safer trick, although a bit longer to write.

dejlek commented on 6758987 Dec 28, 2011

A big step forward! :)


Hmm, i have mixed feeling about this. Syntax isn't very Dish.

Also how is able compile know what this means:

void main() {
  int a = 5;
  auto x =  a => a/2;

is x an bool x = true, or is x a delegate?

a -> a/2;

would be much better syntax (somehow borowed from Erlang).

Any way I do not really think it is needed, because simple lambdas already can be expressed very shortly:

I also doesn't understand how actually it work:

a => a+1;

is equivalent to

delegate (a) { return a+1; };

But this is illegal construction, because there is no type of a. (And I checked grammar it is needed, before 'Declarator' rule).

It may possibly mean, delegate (T)(a) { return a+1; }, but there is no such things as delegate templates in D.


=> isn't a comparison operator, so your first example is unambiguous.

As for how it works: the types are inferred, of course, but that would be too useful for general use to make it general. Much like how the 'auto' return type mysteriously only works on templated functions.

donc commented on 6758987 Jan 22, 2012

Much like how the 'auto' return type mysteriously only works on templated functions.
What do you mean? This compiles:
auto blah() { return 3; }


This have nothing to do with auto return type. It is very simple how it works. It just uses type of expression passed to return(s), or void. Nothing mysterious here.

delegate (double a) { return a+1; }

is actually

delegate auto(double a) { return a+1; }

My question was that

delegate (a) { return a+1; }
delegate auto(a) { return a+1; }

looks to be polymorphic function/delegate, just like

delegate (T)(T a) { return a+1; }

But it is incorrect also (there is no such things like delegate templates.

So I do not really understand how a=>a+1; or delegate (a) { return a+1; } is correct in example with bar39!(a => a + 1)(3);

Similary what with other examples without argument types or multiple arguments?

(a) => (a+1); // is this correct?
(double a, double b) => (a>b); // should be correct, right?
(a,b) => (a>b);  // is this correct ?

As of =>, you are correct, it is distinct from >=. But still -> will make me fell me and my brain much better (it is more obvious, it is different from =>). => have oviously some advantages, like being similar to mathemtical inference symbol, but -> on the other hand is more similar to function mapping symbol. Which is exactly what we want here.


One more non obvious example:

x => y >= x;
x => y => x;

I think,

x -> y >= x;
x -> y -> x;
x -> y -> x+y;

is MUCH clearer. Not only this syntax is used in Erlang, but also in Haskell. => is more from C# I think, or Scala. Was there any discussion about which to choose? I do not follow much newsgroup recently so have no idea.


=> is a C# lambda operator so it's common for many people.

Type of expression like a => a is inferred from using.

auto func = a => a; // error
int function(int) d = a=>a; // compiles

If it is passed as alias template parameter, the expression is passed (without instantiation). One can call t!(a=>a)(); or t!((a){ return a; })(); where t is:

void t(alias f)() {
    pragma(msg, typeof( f(1)   )); // prints int,    like (a=>a)(1)
    pragma(msg, typeof( f(1.0) )); // prints double, like (a=>a)(1.0)

Interesting, still -> is cleaner, and much more intuitive, especially when mixing with >=. It is also used in Haskell, Erlang, OCaml, C++11, and classic lambda calculus notation. I also think that => is bad choice, not only because it is less clear than ->, but now it could be possible (in theory) that one will write => when one should >= and program will compile anyway without error message.

Maybe we cannot use ->, because it is dereferencing operator in C/C++? But AFAIK there is no dereferencing operator in D. There is no such token as -> in lexer.

So compiler now perform lazy typing in some sense, because before that D was only doing forward type inference. Now it is more like full type inference known from Haskell or OCaml (aka Hindley–Milner algorithm).

Anyway D grammar documentation still needs updating, because it says:

  Identifier => AssignExpression
  ParameterAttributes => AssignExpression

and that first form is equivalent to

delegate ( Identifier ) { return AssignExpression; }

but, looking at delegate rules:

    function Type_opt ParameterAttributes_opt FunctionBody
    delegate Type_opt ParameterAttributes opt FunctionBody
    ParameterAttributes FunctionBody

    Parameters FunctionAttributes

    ( ParameterList )
    ( )

    Parameter , ParameterList

    InOut_opt BasicType Declarator
    InOut_opt BasicType Declarator ...
    InOut_opt BasicType Declarator = DefaultInitializerExpression
    InOut_opt Type
    InOut_opt Type ...

So it looks that it is impossible to derive expression:

delegate (x) { return x; }

because there must be a BasicType before 'x'.


If something compiles in both C and D it should behave the same way if there is no strong reason against. With -> as lambda operator e.g, f(x->y); can choose incorrect overload.


There is no function overloads in C. You probably was thinking about C++. If anybody will try to source port C++ source (very bad idea) and will forgot to change -> (dereferencing) to dot (.), (which can be done automatically very easly anyway!) then it is very unlikly there will be anything which will match delegate (->), because there is no such constructs in C++. Somehow artificial example IMHO.

Also it will need even more type inferance to compile automatically, like:

auto f(delegate int(int) dg) {
   return dg(3);
f(x => x+2); //     f(x->x+2);     ,  like (x->x)+2 vs x->(x+2);, second one being inccorrect in C++

will this even work? I guess maybe, because you given similar example:

delegate int(int) dg = x => 13*x;
delegate int(int) dg = (x => 13*x);
Poita commented on 6758987 Jan 22, 2012

@baryluk: I don't find it any cleaner or more intuitive. This is just pointless bikeshedding.


I knew you will say "There is no function overloads in C", but with an exclamation mark or two. I mean, that and overload accepting a function is in D code only (it can be a result of renaming f_func, e.g.).

will this even work?

Yes, if you will change delegate int(int) to int delegate(int).


@Poita: If improving language (to have less potential errors), discussion about incorrect current grammar is pointless then I do not know why we have discussions about language new features and improvements anyway. If you think -> is not cleaner or intuitive, then it is only you opinion (maybe you have been using C#, or something; I guess some people can like one or another). Was asking if there was some discussion or poll about choising => (I guess borrowed from C#, but still how much people come to D from C#, compared to other languages), and I cannot find anything.


@denis-sh: Thanks. Will think more about it. It indeed may be good reason not to use ->.

BTW, slightl offtopic: Still do not understand why D have delegate int(int x) { ... } construct when doing expression, but int delegate(int) as it's type.

Still grammar needs update. ;)

Please sign in to comment.