www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - a lambda with arguments has type void?

reply cy <dlang verge.info.tm> writes:
This program errors out, when I try to pass lambdas that take 
arguments. How am I getting the syntax wrong here? Is there some 
reason you can't do this?

import std.stdio;

void foo(Callable)(Callable bar) {
   bar();
}

void foo2(Callable)(Callable bar, int baz) {
   bar(baz);
}

void main() {
   foo({
	  writeln("okay");
	});
   foo2((bar) {
	  writeln("got",bar);
	},42);
   foo2((bar) => writeln("yay",bar), 42);
}
Jun 07 2016
parent reply ag0aep6g <anonymous example.com> writes:
On 06/08/2016 12:02 AM, cy wrote:
 import std.stdio;

 void foo(Callable)(Callable bar) {
    bar();
 }

 void foo2(Callable)(Callable bar, int baz) {
    bar(baz);
 }

 void main() {
    foo({
        writeln("okay");
      });
    foo2((bar) {
        writeln("got",bar);
      },42);
    foo2((bar) => writeln("yay",bar), 42);
 }
You don't specify the types of the parameters of the function literals, so you effectively have templates there. As such the literals have no types, and can't be passed as arguments. That they're called with an int in foo2's body is not taken into consideration at that point. You can: * make it `int bar` in the literals, or * explicitly take a `void delegate(int)` or `void function(int)` in foo2, or * take the callback as a "template alias parameter": ---- import std.stdio; void foo2(alias bar)(int baz) { bar(baz); } void main() { foo2!((bar) { writeln("got", bar); })(42); foo2!(bar => writeln("yay", bar))(42); } ----
Jun 07 2016
parent reply cy <dlang verge.info.tm> writes:
On Tuesday, 7 June 2016 at 22:17:03 UTC, ag0aep6g wrote:
 You don't specify the types of the parameters of the function 
 literals, so you effectively have templates there. As such the 
 literals have no types, and can't be passed as arguments.
Yeah, I see that now. The compiler does have all the necessary information to infer the argument types in both templates, though. There's no reason that it /couldn't/ infer the type of the argument. Like this is why it doesn't really make sense: import std.stdio; auto foo(Callable)(Callable c) { return c(42); } auto foo2(alias c)() { return c(42); } void main() { // this works, when you know it's an int delegate(int) beforehand... writeln(foo!(int delegate(int))((arg) => arg + 1)); // and this can infer that your argument is an int delegate(int) writeln(foo2!((arg) => arg + 1)); // so why doesn't this work, if the compiler can infer that the // argument is an int delegate(int)? static assert(!__traits(compiles, writeln(foo((arg) => arg + 1)))); } My guess the reason this doesn't work is: nobody worked on it yet.
 You can:
 * make it `int bar` in the literals, or
Sure, but it's a bit of a pain to have to export SomeArgumentType just so my callbacks can go ((SomeArgumentType rows) => ...). I don't like anything that makes you have to go through even more trouble to specify your imported symbols explicitly.
 * explicitly take a `void delegate(int)` or `void 
 function(int)` in foo2,
The problem with that is you don't always know if it's going to be a void delegate(int) or a void function(int). You can use "toDelegate" to force the issue, but that's more boilerplate required for the caller, and really that's the purpose of templates, to adjust your code to fit whether a function pointer or a delegate pointer or whatever is being passed. Or you could overload the function, copying and pasting all the code, one for "void delegate(int)" and one for "void function(int)". Which is exactly what templates are SUPPOSED to prevent, but in this case they just... don't.
 * take the callback as a "template alias parameter":
Sure, that works great, except when the source is a member function. Then it doesn't work at all. But since D has the trick, where you can take a non-member-function and make it act like one, it's workable. Like this is the problem: import std.stdio; version(logical) { struct foo { int arg; void bar(alias c)() { c("foobar",arg); } } } else { struct foo { int arg; } void bar(alias c, foo)(ref foo f) { c("foobar",f.arg); } void bar(alias c, foo)(foo* f) { return bar!(c,foo)(*f); } } void main() { import std.stdio; foo f = foo(42); f.bar!((message,arg) => writeln(message,arg)); } Not to mention if "foo/bar" is defined in a support module, you now have to separately import "bar" to get "struct foo" to work right, since it won't import along with "foo", which makes it more difficult to use explicitly named imports, and I try never to do that. Anything that makes it more difficult to explicitly name imports encourages people to write BAD CODE THAT NOBODY CAN READ BECAUSE WHERE DID THAT SYMBOL COME FROM AAAARGH ahem It's probably the lesser of three evils, using non-member-function templates, to take a template as a callback. I can definitely see a use for being able to specialize templates using inferred types, rather than ones explicitly passed to the !() list.
Jun 08 2016
next sibling parent ketmar <ketmar ketmar.no-ip.org> writes:
On Thursday, 9 June 2016 at 05:20:46 UTC, cy wrote:
 On Tuesday, 7 June 2016 at 22:17:03 UTC, ag0aep6g wrote:
 You don't specify the types of the parameters of the function 
 literals, so you effectively have templates there. As such the 
 literals have no types, and can't be passed as arguments.
Yeah, I see that now. The compiler does have all the necessary information to infer the argument types in both templates, though. There's no reason that it /couldn't/ infer the type of the argument.
yes, you can find alot of situations where it is theoretically possible to infer types (sometimes by doing several passes to "refine" the info), but dmd currently doesn't do that. partially because it's a good amount of work, partially because it will slow down the compiler considerably in some cases.
Jun 08 2016
prev sibling parent Artur Skawina via Digitalmars-d-learn <digitalmars-d-learn puremagic.com> writes:
On 06/09/16 07:20, cy via Digitalmars-d-learn wrote:
 Like this is why it doesn't really make sense:
 
 import std.stdio;
 
 auto foo(Callable)(Callable c) {
   return c(42);
 }
 
 auto foo2(alias c)() {
   return c(42);
 }
 
 void main() {
   // this works, when you know it's an int delegate(int) beforehand...
   writeln(foo!(int delegate(int))((arg) => arg + 1));
   // and this can infer that your argument is an int delegate(int)
   writeln(foo2!((arg) => arg + 1));
No. `a=>a+1` is a /template/ and is passed as-is to `foo2`. Hence `c` is a template and it's only instantiated inside that function. This would compile too: auto foo2(alias c)() { return c(3.14); }
   // so why doesn't this work, if the compiler can infer that the
   // argument is an int delegate(int)?
   static assert(!__traits(compiles,
      writeln(foo((arg) => arg + 1))));
 }
It can't. It's a template. Templates are not values and can not be used as runtime function arguments. The only magic that the compiler does is that it lets you call callable templates directly - it automatically instantiates them (using the types of the arguments used for that call). artur
Jun 09 2016