www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - interface inference with delegates/functions

reply axricard <axelrwiko gmail.com> writes:
Basically the same question than 
https://forum.dlang.org/post/aoltazzfmnztsyatfuft forum.dlang.org 
but with functions/delegates and inferred return types:

``` D
interface I {
         bool check();
}
class A : I {
         bool check() =>true;
}
class B : I {
         bool check() =>false;
}

void main()
{
     I myI = () {
     	final switch("A")
         {
         case "A": return new A();
         case "B": return new B();
         }
     }();
}
```

This won't compile, because return type "Return type of `A` 
inferred here.".

My understanding from the spec 
(https://dlang.org/spec/expression.html#lambda-return-type) is 
that it should pick `I` as return type, because it is different 
from the initial return type (`A`) and a `A` implicitly converts 
to `I`.

Is it an expected behavior ?
Feb 04
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Tuesday, February 4, 2025 5:53:02 AM MST axricard via Digitalmars-d-learn
wrote:
 Basically the same question than
 https://forum.dlang.org/post/aoltazzfmnztsyatfuft forum.dlang.org
 but with functions/delegates and inferred return types:

 ``` D
 interface I {
          bool check();
 }
 class A : I {
          bool check() =>true;
 }
 class B : I {
          bool check() =>false;
 }

 void main()
 {
      I myI = () {
       final switch("A")
          {
          case "A": return new A();
          case "B": return new B();
          }
      }();
 }
 ```

 This won't compile, because return type "Return type of `A`
 inferred here.".

 My understanding from the spec
 (https://dlang.org/spec/expression.html#lambda-return-type) is
 that it should pick `I` as return type, because it is different
 from the initial return type (`A`) and a `A` implicitly converts
 to `I`.

 Is it an expected behavior ?
Yes, it's expected, though that section of the spec could probably be better. Honestly, with regards to initializing variables, type inference is kind of a mess. In the vast majority of cases, the compiler will determine the type of an expression independently of where it's used, since it gets to be a bit of a complicated mess if it attempts to change the type of the expression based on where it's used, and in most cases, Walter has rejected language changes which would involve determining the type of an expression based on its context. However, in the case of initializing or assigning to variables from literals, the compiler will _sometimes_ change the type of a literal based on how it's used in an attempt to be user friendly. For instance, ubyte[] a = [1, 2, 3]; will compile even though typeof([1, 2, 3]) is int[], and you can't initialize a ubyte[] with an int[], since the way that they're laid out in memory is not the same. If the compiler were strict about it, you'd have to do something like ubyte[] a = [ubyte(1), ubyte(2), ubyte(3)]; which is kind of ugly, which is why the compiler interprets the literal differently in this case. However, even worse, ubyte[] a = cast(ubyte[])[1, 2, 3]; is the same as ubyte[] a = [ubyte(1), ubyte(2), ubyte(3)]; even though casting an int[] to a ubyte[] when literals aren't involved does not convert the individual elements but instead reinterprets the memory, which gives a very different result. So, when the compiler tries to be user friendly about this sort of thing by allowing stuff that wouldn't normally be allowed, it tends to be confusing and inconsistent. So, it wouldn't surprise me at all if various parts of the spec need clarification with regards to this sort of thing. Looking at the section of the spec that you linked to, all of the type inference that it's dealing with has to do with function pointers. The compiler is implictly converting a function literal to match the function pointer that it's being used to initialize, and it's allowing that implict conversion based on the return type. So, it's taking a function literal that's returning an int, and it's implicitly converting it to a function that returns long. And in the examples, the return type of the function literal is known independently of its context and then adjusted based on its context. However, in your example, you have a function literal that cannot compile on its own. The compiler's return type inference does not look for a common implicit conversion with classes like this. So, auto myI = () { final switch("A") { case "A": return new A(); case "B": return new B(); } }(); isn't going to compile. Rather, it just takes the first return type and then attempts to implicitly convert the rest of the return values to that type just like if the function had explicitly been marked as returning A. So, for your example to work, the compiler would either need to attempt to find a common subtype (which it doesn't), or it would need to take the return type from the context and apply that to the lambda and _then_ try to compile the lambda, whereas what's pretty clearly happening is that it's compiling the lambda and _then_ adjusting the return type based on the context. So, the lambda needs to be able to compile on its own, which yours doesn't. Now, looking carefully at the spec, I'd say that it matches this behavior. First it says that "The return type of the FunctionLiteral can be inferred from either the AssignExpression, or any ReturnStatements in the BlockStatement." Note that it does not say that it _will_ infer it, just that it _can_ (which of course leaves the door open for the compiler implementation to change as well as be seemingly very inconsistent about what it does). So, arguably, you can't rely on such inference actually occurring, which isn't great. As for the next sentence, "If there is a different expected type from the context, and the initial inferred return type implicitly converts to the expected type, then the return type is inferred as the expected type." Note that it talks about the "initial inferred return type", whereas your function doesn't have an initial inferred return type, because it fails to compile when determining the return type. So, while the spec doesn't expand on what it's saying here to give more detail, I'd say that it's definitely saying that your lambda needs to compile and thus have an inferred return type, and _then_ the context comes into play. And honestly, I'm pretty sure that the entire intent of this portion of the spec has to do with function pointers and not about converting the return type when calling the function. All of the conversions that it's talking about have to do with function pointers, and if your lambda were inferred to have a return type that implicitly converted to I, then there would be no need for further inference based on the context, since the return value would just convert to I without any special casing required - whereas function pointers require some special casing in order to compile (similar to how ubyte[] requires some special casing to support [1, 2, 3]). So, the spec could definitely be clearer, but it does seem to be talking entirely about adjusting the return type of a lambda that already has a return type, not about inferring the initial return type of the lambda based on its context. It has to have an initial return type to adjust first. - Jonathan M Davis
Feb 04
parent axricard <axelrwiko gmail.com> writes:
On Wednesday, 5 February 2025 at 01:20:07 UTC, Jonathan M Davis 
wrote:
 Note that it talks about the "initial inferred return type", 
 whereas your function doesn't have an initial inferred return 
 type, because it fails to compile when determining the return 
 type. So, while the spec doesn't expand on what it's saying 
 here to give more detail, I'd say that it's definitely saying 
 that your lambda needs to compile and thus have an inferred 
 return type, and _then_ the context comes into play.

 And honestly, I'm pretty sure that the entire intent of this 
 portion of the spec has to do with function pointers and not 
 about converting the return type when calling the function.
Thanks for the clarification. Indeed this compiles if we separate function definition from its call : ``` D I delegate() getI = () { final switch("A") { case "A": return new A(); case "B": return new B(); } }; I myI = getI(); ``` But I guess the most readable form for my case is to simply avoid inference : ``` D I myI = delegate I() { final switch("A") { case "A": return new A(); case "B": return new B(); } }(); ```
Feb 05