Thread overview
How to declare "type of function, passed as an argument, which should have it's type inferred"? (or if D had an "any" type)
Mar 29, 2021
Gavin Ray
Mar 29, 2021
Ali Çehreli
Mar 29, 2021
Paul Backus
Mar 29, 2021
Gavin Ray
Mar 29, 2021
evilrat
Mar 29, 2021
Gavin Ray
Mar 29, 2021
evilrat
March 29, 2021
Brief question, is it possible to write this so that the "alias fn" here appears as the final argument?

  auto my_func(alias fn)(string name, string description, auto otherthing)

The above seems to work, since the type of "fn" can vary and it gets called inside of "my_func", but from an ergonomics point I'd love to be able to put the function last and write it inline:

  my_func("name", "description", "otherthing", (x, y, z) {
    auto foo = 1;
    return foo + 2;
  })


Currently I am calling it like:

  auto some_func = (x, y, z) {
    auto foo = 1;
    return foo + 2;
  };

  my_func!some_func("name", "description", "otherthing");

Which is fine, it's just that from a readability perspective it doesn't really allow for writing the function inline and you need to declare it separately.

Not a huge deal, just learning and thought I would ask =)

Thank you!
March 29, 2021
On 3/29/21 8:13 AM, Gavin Ray wrote:

> Brief question, is it possible to write this so that the "alias fn" here
> appears as the final argument?
>
>    auto my_func(alias fn)(string name, string description, auto otherthing)

Yes, as a type template parameter but you would have to constrain the parameter yourself (if you care):

import std.stdio;
import std.traits;
import std.meta;

auto myFunc(F)(string name, F func)
{
  // This condition could be a template constraint but they don't
  // support error messages.
  static assert (is (Parameters!func == AliasSeq!(string)),
                 "'func' must be a callable that takes 'string'.");
  return func(name);
}

void main() {
  // One trouble with this "solution" is that for the compiler to
  // know the return type of the lambda, the parameter must be
  // declared as 'string' (in this case).
  writeln(myFunc("foo", (string a) => a ~ '.'));
}

Ali

March 29, 2021
On Monday, 29 March 2021 at 16:20:59 UTC, Ali Çehreli wrote:
> auto myFunc(F)(string name, F func)
> {
>   // This condition could be a template constraint but they don't
>   // support error messages.
>   static assert (is (Parameters!func == AliasSeq!(string)),
>                  "'func' must be a callable that takes 'string'.");
>   return func(name);
> }
>
> void main() {
>   // One trouble with this "solution" is that for the compiler to
>   // know the return type of the lambda, the parameter must be
>   // declared as 'string' (in this case).
>   writeln(myFunc("foo", (string a) => a ~ '.'));
> }
>
> Ali

Alternatively:

    auto myFunc(F)(string name, F func)
    {
      static assert (__traits(compiles, (string s) => func(s)),
                     "'func' must be a callable that takes 'string'.");
      return func(name);
    }

    void main() {
      // No need to write out the argument type
      writeln(myFunc("foo", a => a ~ '.'));
    }

You can generalize this into a helper template:

    enum bool isCallableWith(alias fun, ArgTypes...) =
      __traits(compiles, (ArgTypes args) => fun(args));

Usage:

    static assert(isCallableWith!(func, string));
March 29, 2021
On Monday, 29 March 2021 at 15:13:04 UTC, Gavin Ray wrote:
> Brief question, is it possible to write this so that the "alias fn" here appears as the final argument?
>
>   auto my_func(alias fn)(string name, string description, auto otherthing)
>
> The above seems to work, since the type of "fn" can vary and it gets called inside of "my_func", but from an ergonomics point I'd love to be able to put the function last and write it inline:
>
>   my_func("name", "description", "otherthing", (x, y, z) {
>     auto foo = 1;
>     return foo + 2;
>   })
>
>
> Currently I am calling it like:
>
>   auto some_func = (x, y, z) {
>     auto foo = 1;
>     return foo + 2;
>   };
>
>   my_func!some_func("name", "description", "otherthing");
>
> Which is fine, it's just that from a readability perspective it doesn't really allow for writing the function inline and you need to declare it separately.
>
> Not a huge deal, just learning and thought I would ask =)
>
> Thank you!

Also with delegates (lazy), you get the type checks however you must have to declare parameters on call site, which can be PITA in the future when doing refactoring will be necessary.

Better plan ahead as the number of changes will explode when you make quite a number of these and decide to change params/returns.

```
  import std.stdio;

  void my_func(T, XS)(string a, string b, string c, lazy T function(XS)[] t...)
  {
    // call function, just the first one, can call all of them as well
    t[0](a);

    // can get the result too, mind the future refactoring needs tho
    // T res = t[0]();
  }

  void main()
  {
    my_func("name", "description", "otherthing", (string x) {
      writeln(x);
      return x;
    });

    // no function, compile error
    // my_func("name", "description", "otherthing");
  }
```
March 29, 2021
On Monday, 29 March 2021 at 16:31:49 UTC, Paul Backus wrote:
> On Monday, 29 March 2021 at 16:20:59 UTC, Ali Çehreli wrote:
>> auto myFunc(F)(string name, F func)
>> {
>>   // This condition could be a template constraint but they don't
>>   // support error messages.
>>   static assert (is (Parameters!func == AliasSeq!(string)),
>>                  "'func' must be a callable that takes 'string'.");
>>   return func(name);
>> }
>>
>> void main() {
>>   // One trouble with this "solution" is that for the compiler to
>>   // know the return type of the lambda, the parameter must be
>>   // declared as 'string' (in this case).
>>   writeln(myFunc("foo", (string a) => a ~ '.'));
>> }
>>
>> Ali
>
> Alternatively:
>
>     auto myFunc(F)(string name, F func)
>     {
>       static assert (__traits(compiles, (string s) => func(s)),
>                      "'func' must be a callable that takes 'string'.");
>       return func(name);
>     }
>
>     void main() {
>       // No need to write out the argument type
>       writeln(myFunc("foo", a => a ~ '.'));
>     }
>
> You can generalize this into a helper template:
>
>     enum bool isCallableWith(alias fun, ArgTypes...) =
>       __traits(compiles, (ArgTypes args) => fun(args));
>
> Usage:
>
>     static assert(isCallableWith!(func, string));

------------------------------------------------------------------------

Ah that was even easier than I had made it out to be, thank you folks!

The part about needing to define the argument types to the lambda is no problem either (it was a silly transcription mistake on my end).

I actually need those argument types for later, to generate code based on them (translating D arg types -> a subset of C types, for building a translated function signature string to give to another app) so it works out =D

Much appreciated.
March 29, 2021
On Monday, 29 March 2021 at 17:02:40 UTC, evilrat wrote:
>
> Also with delegates (lazy), you get the type checks however you must have to declare parameters on call site, which can be PITA in the future when doing refactoring will be necessary.
>
> Better plan ahead as the number of changes will explode when you make quite a number of these and decide to change params/returns.
>
> ```
>   import std.stdio;
>
>   void my_func(T, XS)(string a, string b, string c, lazy T function(XS)[] t...)
>   {
>     // call function, just the first one, can call all of them as well
>     t[0](a);
>
>     // can get the result too, mind the future refactoring needs tho
>     // T res = t[0]();
>   }
>
>   void main()
>   {
>     my_func("name", "description", "otherthing", (string x) {
>       writeln(x);
>       return x;
>     });
>
>     // no function, compile error
>     // my_func("name", "description", "otherthing");
>   }
> ```
-----------------------------------------------------------------------

Trying to read this function signature:

    void my_func(T, XS)(string a, string b, string c, lazy T function(XS)[] t...)

Does this say "Generic void function 'my_func', which takes two generic/type params "T" and "XS", and is a function of type "string a, string b, string c", and..." (this is where it starts to get hazy for me):

How does one interpret/read this:

   lazy T function(XS)[] t...

Also I noticed that no explicit generic types were provided in your call. I assume this means that D's type system is similar to Typescript's then, where it's a logical constraints and will try to "fit the holes" so to speak.

In Typescript it works like this:

  function myFunc<T>(arg: T) {}
  myFunc(1) // T is inferred to be type "number"
  myFunc("a") // T is inferred to be type "string"
  myFunc<number>(1) // Same as above, but explicit, maybe useful if you want to verify arg, else pointless

It seems like potentially D is similar here?

March 29, 2021
On Monday, 29 March 2021 at 17:52:13 UTC, Gavin Ray wrote:
> Trying to read this function signature:
>
>     void my_func(T, XS)(string a, string b, string c, lazy T function(XS)[] t...)
>
> Does this say "Generic void function 'my_func', which takes two generic/type params "T" and "XS", and is a function of type "string a, string b, string c", and..." (this is where it starts to get hazy for me):
>
> How does one interpret/read this:
>
>    lazy T function(XS)[] t...

A tuple of functions that takes XS argument(s) and returns T.

'...' part is a tuple/variadic arguments, however with function it is somewhat wacky and requires an array notation [], otherwise compiler treats it like a mere pointer for some reason, same with t[0]() call.

Also even though it says just XS in function parameters it has this special meaning, basically 'argument list'. (you can do cool tricks like this https://dlang.org/phobos/std_traits.html#Parameters)

lazy is parameter storage on function argument.

> Also I noticed that no explicit generic types were provided in your call. I assume this means that D's type system is similar to Typescript's then, where it's a logical constraints and will try to "fit the holes" so to speak.
>
> In Typescript it works like this:
>
>   function myFunc<T>(arg: T) {}
>   myFunc(1) // T is inferred to be type "number"
>   myFunc("a") // T is inferred to be type "string"
>   myFunc<number>(1) // Same as above, but explicit, maybe useful if you want to verify arg, else pointless
>
> It seems like potentially D is similar here?

I'm not that familiar with TypeScript but it looks close enough to what C# and C++ does, but yes it is like you described it, except explicit types is not to verify but to force it to be of that type when compiler is too confused or you need to use some specific base class or interface.