Thread overview
Deprecate lambdas that omit parentheses in empty parameter lists

4 days ago

Quirin Schroll

2 days ago

Nick Treleaven

Jun 15

Dukc

June 13

Deprecate lambdas that omit parentheses in empty parameter lists
Gravatar of ABrightLightPosted by ABrightLightPermalinkReply
Gravatar of ABrightLight

It's generally been agreed on in the past that {} without () in lambdas causes ambiguity with block statements.

void foo (void delegate () dg) {
  dg();
}

void main () {
  auto dg = {1.writeln;}; // Works.

  foo(dg); // function `foo` is not callable using argument types `(void function() @safe)` (This is not entirely related but I thought I'd point this out as something a bit odd)

  {1.writeln;}(); // Error
}

These are the simpler cases where it could be a problem. But I recently thought of lazy, and how expression-statements are coerced to delegates under the hood, but what if you wanted to pass a block statement to a function that takes lazy?

Anyway, I'd rather not have to juggle these questions in my mind, and a simple solution would be to deprecate function literals that omit the parentheses for the parameter list. Having to type an extra 2 characters is well worth not having to think about the edge cases.

Thank you

June 13

Re: Deprecate lambdas that omit parentheses in empty parameter lists
Gravatar of Nick TreleavenPosted by Nick Treleaven
in reply to ABrightLight
PermalinkReply

Nick Treleaven

Gravatar of Nick Treleaven

Posted in reply to ABrightLight

On Saturday, 13 June 2026 at 00:05:38 UTC, ABrightLight wrote:

>

It's generally been agreed on in the past that {} without () in lambdas causes ambiguity with block statements.

void foo (void delegate () dg) {
  dg();
}

void main () {
  auto dg = {1.writeln;}; // Works.

  foo(dg); // function `foo` is not callable using argument types `(void function() @safe)` (This is not entirely related but I thought I'd point this out as something a bit odd)

Function literals have their attributes inferred unless one initializes a declaration and the type is specified. dg in main is inferred @safe, so it cannot implicitly convert to @system.

>

  {1.writeln;}(); // Error
}

See https://github.com/dlang/dmd/issues/18021.

>

These are the simpler cases where it could be a problem. But I recently thought of lazy, and how expression-statements are coerced to delegates under the hood, but what if you wanted to pass a block statement to a function that takes lazy?

Do you mean an expression is coerced to a delegate? And passing a nullary function literal? That doesn't seem to work well due to likely inferred attribute mismatch, regardless of literal syntax:

void f(lazy int l);

void main()
{
    f(() { return 1; }); // Error
}

>

Anyway, I'd rather not have to juggle these questions in my mind, and a simple solution would be to deprecate function literals that omit the parentheses for the parameter list. Having to type an extra 2 characters is well worth not having to think about the edge cases.

I agree in theory. I don't know how much code it would break (e.g. when upgrading edition).

June 13

Re: Deprecate lambdas that omit parentheses in empty parameter lists
Gravatar of Nick TreleavenPosted by Nick Treleaven
in reply to Nick Treleaven
PermalinkReply

Nick Treleaven

Gravatar of Nick Treleaven

Posted in reply to Nick Treleaven

On Saturday, 13 June 2026 at 16:25:15 UTC, Nick Treleaven wrote:

>

On Saturday, 13 June 2026 at 00:05:38 UTC, ABrightLight wrote:

>

These are the simpler cases where it could be a problem. But I recently thought of lazy, and how expression-statements are coerced to delegates under the hood, but what if you wanted to pass a block statement to a function that takes lazy?

Do you mean an expression is coerced to a delegate? And passing a nullary function literal? That doesn't seem to work well due to likely inferred attribute mismatch, regardless of literal syntax:

Sorry, it does work actually:

import std.stdio;

void f(lazy int l) {
    l().writeln;
}

void main()
{
    f({ return 1; }());
}

June 14

Re: Deprecate lambdas that omit parentheses in empty parameter lists
Gravatar of ABrightLightPosted by ABrightLight
in reply to Nick Treleaven
PermalinkReply

ABrightLight

Gravatar of ABrightLight

Posted in reply to Nick Treleaven

On Saturday, 13 June 2026 at 16:28:13 UTC, Nick Treleaven wrote:

>

On Saturday, 13 June 2026 at 16:25:15 UTC, Nick Treleaven wrote:

>

On Saturday, 13 June 2026 at 00:05:38 UTC, ABrightLight wrote:

>

These are the simpler cases where it could be a problem. But I recently thought of lazy, and how expression-statements are coerced to delegates under the hood, but what if you wanted to pass a block statement to a function that takes lazy?

Do you mean an expression is coerced to a delegate? And passing a nullary function literal? That doesn't seem to work well due to likely inferred attribute mismatch, regardless of literal syntax:

Sorry, it does work actually:

import std.stdio;

void f(lazy int l) {
    l().writeln;
}

void main()
{
    f({ return 1; }());
}

The issue with lazy in such examples is that normally expressions get coerced to a delegate that then returns the results of evaluating those expressions. But if you pass function pointers or delegates to lazy, there is ambiguity.

import std.stdio;

void f (lazy void dg) {
    dg; // Same as dg().
}

void main()
{
    auto dg = () { writeln(1); };
    f(dg); // Error: `dg` has no effect.
    f(dg());
}
import std.stdio;

void f (lazy int dg) {
    dg;
}

void main()
{
    auto dg = () { return 1; };
    f(dg); /*
onlineapp.d(10): Error: function `f` is not callable using argument types `(int function() pure nothrow @nogc @safe)`
    f(dg); // Error: `dg` has no effect.
     ^
onlineapp.d(10):        cannot pass argument `dg` of type `int function() pure nothrow @nogc @safe` to parameter `lazy int dg`
onlineapp.d(3):        `onlineapp.f(lazy int dg)` declared here
void f (lazy int dg) {
     ^
*/
    f(dg());
}

However I agree with you that inference is also a problem here.

June 14

Re: Deprecate lambdas that omit parentheses in empty parameter lists
Gravatar of Nick TreleavenPosted by Nick Treleaven
in reply to ABrightLight
PermalinkReply

Nick Treleaven

Gravatar of Nick Treleaven

Posted in reply to ABrightLight

On Sunday, 14 June 2026 at 05:07:58 UTC, ABrightLight wrote:

>

On Saturday, 13 June 2026 at 16:28:13 UTC, Nick Treleaven wrote:

>

Sorry, it does work actually:

import std.stdio;

void f(lazy int l) {
    l().writeln;
}

void main()
{
    f({ return 1; }());
}

The issue with lazy in such examples is that normally expressions get coerced to a delegate that then returns the results of evaluating those expressions. But if you pass function pointers or delegates to lazy, there is ambiguity.

import std.stdio;

void f (lazy void dg) {
    dg; // Same as dg().
}

void main()
{
    auto dg = () { writeln(1); };
    f(dg); // Error: `dg` has no effect.
    f(dg());
}

The parameter type is void, whereas you are passing an argument of type void function(). You have to call the delegate argument.

Anyway I thought your original point was about a lazy parameter being passed a { } function literal? It seems requiring () { } wouldn't be any different.

>

import std.stdio;

void f (lazy int dg) {
    dg;
}

void main()
{
    auto dg = () { return 1; };
    f(dg); /*
onlineapp.d(10): Error: function `f` is not callable using argument types `(int function() pure nothrow @nogc @safe)`
    f(dg); // Error: `dg` has no effect.
     ^
onlineapp.d(10):        cannot pass argument `dg` of type `int function() pure nothrow @nogc @safe` to parameter `lazy int dg`
onlineapp.d(3):        `onlineapp.f(lazy int dg)` declared here
void f (lazy int dg) {
     ^
*/
    f(dg());
}

However I agree with you that inference is also a problem here.

Another way to avoid that is by using alias dg = instead of a variable.

June 14

Re: Deprecate lambdas that omit parentheses in empty parameter lists
Gravatar of Nick TreleavenPosted by Nick Treleaven
in reply to Nick Treleaven
PermalinkReply

Nick Treleaven

Gravatar of Nick Treleaven

Posted in reply to Nick Treleaven

On Sunday, 14 June 2026 at 15:28:30 UTC, Nick Treleaven wrote:

>

On Sunday, 14 June 2026 at 05:07:58 UTC, ABrightLight wrote:

>

The issue with lazy in such examples is that normally expressions get coerced to a delegate that then returns the results of evaluating those expressions. But if you pass function pointers or delegates to lazy, there is ambiguity.

import std.stdio;

void f (lazy void dg) {
    dg; // Same as dg().
}

OK. It really would be clearer if lazy parameters always had to be explicitly called. Otherwise a lazy parameter can be evaluated more than once accidentally in the function.

June 15

Re: Deprecate lambdas that omit parentheses in empty parameter lists
Gravatar of DukcPosted by Dukc
in reply to ABrightLight
PermalinkReply

Dukc

Gravatar of Dukc

Posted in reply to ABrightLight

On Saturday, 13 June 2026 at 00:05:38 UTC, ABrightLight wrote:

>

It's generally been agreed on in the past that {} without () in lambdas causes ambiguity with block statements.

Let's take an example

{   foo();
    bar();
};

What is this? Either it's a function literal that is not used anyhow, or a block statement followed by an empty statement (the semicolon).

The language disallows the first interpretation, as you'd have to use the value of the function literal somehow, meaning the second interpretation is right. If you meant a function literal, you should use it somehow - call it or assign it to something.

While there's no technical ambiguity, the line between the meanings is thinner than I would like. Maybe we could disallow ; for an empty statement so that you'd have to use {} instead like you already have to for if/for/while/etc.

July 21

Re: Deprecate lambdas that omit parentheses in empty parameter lists
Gravatar of Anton PastukhovPosted by Anton Pastukhov
in reply to Dukc
PermalinkReply

Anton Pastukhov

Gravatar of Anton Pastukhov

Posted in reply to Dukc

On Monday, 15 June 2026 at 11:00:05 UTC, Dukc wrote:

>

On Saturday, 13 June 2026 at 00:05:38 UTC, ABrightLight wrote:

>

[...]

Let's take an example

{   foo();
    bar();
};

What is this? Either it's a function literal that is not used anyhow, or a block statement followed by an empty statement (the semicolon).

The language disallows the first interpretation, as you'd have to use the value of the function literal somehow, meaning the second interpretation is right. If you meant a function literal, you should use it somehow - call it or assign it to something.

While there's no technical ambiguity, the line between the meanings is thinner than I would like. Maybe we could disallow ; for an empty statement so that you'd have to use {} instead like you already have to for if/for/while/etc.

This is a good point. D is too lenient with brackets omission, in this particular case and others. I was bitten by this more than once.

4 days ago

Re: Deprecate lambdas that omit parentheses in empty parameter lists
Gravatar of Quirin SchrollPosted by Quirin Schroll
in reply to Nick Treleaven
PermalinkReply

Quirin Schroll

Gravatar of Quirin Schroll

Posted in reply to Nick Treleaven

On Saturday, 13 June 2026 at 16:25:15 UTC, Nick Treleaven wrote:

>

On Saturday, 13 June 2026 at 00:05:38 UTC, ABrightLight wrote:

>

It's generally been agreed on in the past that {} without () in lambdas causes ambiguity with block statements.

void foo (void delegate () dg) {
  dg();
}

void main () {
  auto dg = {1.writeln;}; // Works.

  foo(dg); // function `foo` is not callable using argument types `(void function() @safe)` (This is not entirely related but I thought I'd point this out as something a bit odd)

Function literals have their attributes inferred unless one initializes a declaration and the type is specified. dg in main is inferred @safe, so it cannot implicitly convert to @system.

The issue isn't attributes (@safe converts to @system) but the mismatch of function (function pointer) vs. delegate. A function pointer doesn't convert to a delegate. A lambda is inferred to become a function pointer if it doesn't capture anything. You can force it to become a delegate type by spelling it out: delegate {…}. (Same with function, then the lambda must not capture anything.)

The issue with this code and D is that lambdas can become two similar yet incompatible things when neither function nor delegate is used.

One mitigation would be to allow in implicit conversion from a function pointer to a delegate. One can do that manually, but it requires repeating the function arguments. It's particularly bad because a lambda function argument behaves differently enough so that it often can't be extracted into a variable. (The variable gets typed as a function pointer, which I already said is incompatible.) Lambdas without captures are in some Schrödinger-style superposition where they can become function pointers or delegates, whatever is needed. If no choice is made, they become function pointers, probably because they use one less pointer and can be optimized more aggressively.

This is similar to array literals which know their size when used immediately: A template can infer a size_t template argument from the size of an array literal passed to it via IFTI. If you assign the array literal to an auto variable instead and use it, the size information is gone and IFTY can't infer the size.

The biggest difference is that array literal become the type with the lower amount of information.

Now that int[$] works, maybe we should add auto[$] which infers the element type as well, but guarantees a static array.

2 days ago

Re: Deprecate lambdas that omit parentheses in empty parameter lists
Gravatar of Nick TreleavenPosted by Nick Treleaven
in reply to Quirin Schroll
PermalinkReply

Nick Treleaven

Gravatar of Nick Treleaven

Posted in reply to Quirin Schroll

On Wednesday, 23 September 2026 at 15:15:58 UTC, Quirin Schroll wrote:

>

On Saturday, 13 June 2026 at 16:25:15 UTC, Nick Treleaven wrote:

>

On Saturday, 13 June 2026 at 00:05:38 UTC, ABrightLight wrote:

>

It's generally been agreed on in the past that {} without () in lambdas causes ambiguity with block statements.

void foo (void delegate () dg) {
  dg();
}

void main () {
  auto dg = {1.writeln;}; // Works.

  foo(dg); // function `foo` is not callable using argument types `(void function() @safe)` (This is not entirely related but I thought I'd point this out as something a bit odd)

Function literals have their attributes inferred unless one initializes a declaration and the type is specified. dg in main is inferred @safe, so it cannot implicitly convert to @system.

The issue isn't attributes (@safe converts to @system) but the mismatch of function (function pointer) vs. delegate. A function pointer doesn't convert to a delegate.

Yes, I clearly wasn't thinking when I wrote that, sorry.

>

A lambda is inferred to become a function pointer if it doesn't capture anything. You can force it to become a delegate type by spelling it out: delegate {…}. (Same with function, then the lambda must not capture anything.)

The issue with this code and D is that lambdas can become two similar yet incompatible things when neither function nor delegate is used.

One mitigation would be to allow in implicit conversion from a function pointer to a delegate.

This discussion says the calling convention is different:
https://digitalmars.com/d/archives/digitalmars/D/implicitly_convert_function_pointers_to_delegates_78746.html

>

Now that int[$] works, maybe we should add auto[$] which infers the element type as well, but guarantees a static array.

That's why I argued for syntax for a static array literal instead of complicating declaration syntax.