www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - typeof function literals which define the types of its parameters but

reply Johannes Loher <johannes.loher fg4f.de> writes:
Hey all,

I am a bit confused about the inferred types of function literals 
which do not name their parameters (something like `(int) {}`). 
The confusion arises from the fact that the inferred type 
sometimes is `void` (might be the case, because the function 
literal is inferred to be a template) and sometimes it is 
something like `void function(bool _param_0) pure nothrow  nogc 
 safe`. What is really weir is that seems to depend on the type 
of the parameter. Here is small example showing what happens for 
different parameter types: https://run.dlang.io/is/xSMZZu

Also note that this only happens for function literals. For 
regular functions and member functions, `void` is never inferred, 
but only types like `pure nothrow  nogc  safe void(MyClass 
_param_0)`.

Has anybody any idea what is going on here? Is this a bug?
Dec 26 2018
next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 12/26/18 10:52 PM, Johannes Loher wrote:
 Hey all,
 
 I am a bit confused about the inferred types of function literals which 
 do not name their parameters (something like `(int) {}`). The confusion 
 arises from the fact that the inferred type sometimes is `void` (might 
 be the case, because the function literal is inferred to be a template) 
 and sometimes it is something like `void function(bool _param_0) pure 
 nothrow  nogc  safe`. What is really weir is that seems to depend on the 
 type of the parameter. Here is small example showing what happens for 
 different parameter types: https://run.dlang.io/is/xSMZZu
 
 Also note that this only happens for function literals. For regular 
 functions and member functions, `void` is never inferred, but only types 
 like `pure nothrow  nogc  safe void(MyClass _param_0)`.
 
 Has anybody any idea what is going on here? Is this a bug?
This is pretty funny actually. Note the difference between the ones that print something other than "void" is that the parameters are all keywords! The ones that do print "void" have a normal symbol name as their parameter. This could be a parameter name or a parameter type, and the compiler is choosing the latter. In other words, you are expecting: alias f = (string) {} to evaluate to something like: void f(string) {} but in fact, it evaluates to: void f(T)(T string) {} because string is not a keyword, it's an alias. So you are free to redefine it inside your lambda (in this case, as a template parameter *name*). As proof, you can try calling it like this: f(1) and it works. To fix, just give all your lambda parameters names. -Steve
Dec 26 2018
parent reply Johannes Loher <johannes.loher fg4f.de> writes:
On Thursday, 27 December 2018 at 04:27:03 UTC, Steven 
Schveighoffer wrote:
 On 12/26/18 10:52 PM, Johannes Loher wrote:
 Hey all,
 
 I am a bit confused about the inferred types of function 
 literals which do not name their parameters (something like 
 `(int) {}`). The confusion arises from the fact that the 
 inferred type sometimes is `void` (might be the case, because 
 the function literal is inferred to be a template) and 
 sometimes it is something like `void function(bool _param_0) 
 pure nothrow  nogc  safe`. What is really weir is that seems 
 to depend on the type of the parameter. Here is small example 
 showing what happens for different parameter types: 
 https://run.dlang.io/is/xSMZZu
 
 Also note that this only happens for function literals. For 
 regular functions and member functions, `void` is never 
 inferred, but only types like `pure nothrow  nogc  safe 
 void(MyClass _param_0)`.
 
 Has anybody any idea what is going on here? Is this a bug?
This is pretty funny actually. Note the difference between the ones that print something other than "void" is that the parameters are all keywords! The ones that do print "void" have a normal symbol name as their parameter. This could be a parameter name or a parameter type, and the compiler is choosing the latter. In other words, you are expecting: alias f = (string) {} to evaluate to something like: void f(string) {} but in fact, it evaluates to: void f(T)(T string) {} because string is not a keyword, it's an alias. So you are free to redefine it inside your lambda (in this case, as a template parameter *name*). As proof, you can try calling it like this: f(1) and it works. To fix, just give all your lambda parameters names. -Steve
Thanks for the insight, this is indeed actually very funny. You can even force the compiler to interpret the parameters as types in several ways: ``` alias j = (typeof(string.init)) { }; alias identity(T) = T; alias k = (identity!size_t) { }; ``` or if the type is defined at module level, this works, too: ``` alias l = (.MyStruct) { }; ``` There are probably a lot more. You just need to use some expression which evaluates to the actual type but cannot be interpreted as an identifier (".", "!", "(" and ")" are not allowed inside identifiers, which is how I suppose the above examples work). However, this still leaves the question whether this behavior is intentional or not. It is at least very surprising and inconsistent in my opinion. For example consider some generic code using `std.traits.isSomeFunction` (which is actually how I found out about this): ``` static assert(isSomeFunction!((long) {}); static assert(!isSomeFunction!((size_t) {}); ``` If this behavior is indeed intentional, it should at least be covered in the spec.
Dec 27 2018
next sibling parent Johannes Loher <johannes.loher fg4f.de> writes:
On Thursday, 27 December 2018 at 08:53:30 UTC, Johannes Loher 
wrote:
 On Thursday, 27 December 2018 at 04:27:03 UTC, Steven 
 Schveighoffer wrote:
 [...]
As a side note: During last DConf I talked to Walter and Andrei about the fact that `typeof(SomeTemplate) == void` and they agreed that it probably should be a compiler error instead (see also https://issues.dlang.org/show_bug.cgi?id=15437). It would be pretty hard to do this right now, as it breaks existing code (quite a lot of phobos code depends on this in template constraints). But if this was indeed some day changed to be a compiler error, it would make things even weirder.
Dec 27 2018
prev sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 27 December 2018 at 08:53:30 UTC, Johannes Loher 
wrote:
 If this behavior is indeed intentional, it should at least be 
 covered in the spec.
I know the template part is intentional (including the identifier thing, function names are allowed to share names with global identifiers and override them locally), though indeed, it giving void I think is a bug. Just in that category where nobody cares enough to put a lot of effort into it.
Dec 27 2018
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 12/27/18 9:45 AM, Adam D. Ruppe wrote:
 On Thursday, 27 December 2018 at 08:53:30 UTC, Johannes Loher wrote:
 If this behavior is indeed intentional, it should at least be covered 
 in the spec.
I know the template part is intentional (including the identifier thing, function names are allowed to share names with global identifiers and override them locally), though indeed, it giving void I think is a bug. Just in that category where nobody cares enough to put a lot of effort into it.
A template evaluating to a type void is how is(typeof(someTemplateDefinition)) works. I don't know how much code would break, but it would not be insignificant if this changed. -Steve
Dec 27 2018
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Thursday, 27 December 2018 at 15:39:23 UTC, Steven 
Schveighoffer wrote:
 A template evaluating to a type void is how 
 is(typeof(someTemplateDefinition)) works.
Yeah, I know of that, but with __traits(isTemplate) now, I don't think there's any good reason to use the old hack detection anymore anyway.
Dec 27 2018
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 12/27/18 10:53 AM, Adam D. Ruppe wrote:
 On Thursday, 27 December 2018 at 15:39:23 UTC, Steven Schveighoffer wrote:
 A template evaluating to a type void is how 
 is(typeof(someTemplateDefinition)) works.
Yeah, I know of that, but with __traits(isTemplate) now, I don't think there's any good reason to use the old hack detection anymore anyway.
Yeah, there's definitely ways to work around the limitation. __traits(compiles) might also help as well. The real question is, does the current behavior lead to many bugs? If not, then even though it's a hack, it's not a harmful one. One possibility I can think of is a function foo that takes a string delegate, you may expect this to work: is(typeof(foo((string) {}))) When really you meant this: is(typeof(foo((string s) {}))) But I wonder how often such a thing happens in practice. -Steve
Dec 27 2018
prev sibling parent Paul Backus <snarwin gmail.com> writes:
On Thursday, 27 December 2018 at 03:52:52 UTC, Johannes Loher 
wrote:
 Hey all,

 I am a bit confused about the inferred types of function 
 literals which do not name their parameters (something like 
 `(int) {}`). The confusion arises from the fact that the 
 inferred type sometimes is `void` (might be the case, because 
 the function literal is inferred to be a template) and 
 sometimes it is something like `void function(bool _param_0) 
 pure nothrow  nogc  safe`. What is really weir is that seems to 
 depend on the type of the parameter. Here is small example 
 showing what happens for different parameter types: 
 https://run.dlang.io/is/xSMZZu

 Also note that this only happens for function literals. For 
 regular functions and member functions, `void` is never 
 inferred, but only types like `pure nothrow  nogc  safe 
 void(MyClass _param_0)`.

 Has anybody any idea what is going on here? Is this a bug?
As you suspect, some of the function literals are being interpreted as templates--specifically, the ones whose parameters are parsed as "identifiers" rather than built-in types. Keep in mind that from the parser's perspective, `string`, `size_t`, and `Object` aren't any different from other user-defined type names or aliases. That they happen to be defined in the D runtime is irrelevant. You can see that this is true by running the following example: alias f = (string) { }; writeln(typeof(f!int).stringof); Unfortunately, this behavior is mentioned exactly nowhere in the language spec. Both the section on function literals [1] and the grammar for function parameters to which it refers [2] fail to mention any circumstance in which a parameter declaration may consist only of a name, without a type. As with many things in D, one can only figure out what the *actual* rules are through trial and error. [1] https://dlang.org/spec/expression.html#function_literals [2] https://dlang.org/spec/function.html#grammar
Dec 26 2018