Type inference for default function / method arguments?

Witold Baryluk witold.baryluk at gmail.com
Mon May 10 18:50:02 UTC 2021


Hi,

using D for very long time.

I was working on a project, and converting bunch of optional 
`bool` options in various functions, to use `Flag` and `BitFlag`, 
to make it more readable, especially on a call side.

Before:

```d
void show_help_and_exit(const string exec_name, const string 
extra_help,
                         bool verbose = false,
                         bool reallyverbose = false,
                         bool shorthelp = false) {
...
}


...
show_help_and_exit(exec_name, extra_help, /*verbose=*/true, 
/*reallyverbose=*/true);

```

So, now with `Flag`, it could look like this:



```d
// rename import, because of collision with my "Flag" classes
import std.typecons : ArgFlag = Flag, Yes, No;

void show_help_and_exit(const string exec_name, const string 
extra_help,
                         ArgFlag!"verbose" verbose = No.verbose,
                         ArgFlag!"reallyverbose" reallyverbose = 
No.reallyverbose,
                         ArgFlag!"shorthelp" shorthelp = 
No.shorthelp) {
...
}

...
show_help_and_exit(exec_name, extra_help, Yes.verbose, 
Yes.reallyverbose);

```


Which is great on a caller side, but is very verbose on the 
function (or method) definition side.

I wish there was a shorter way to express this function 
declaration or definition.

The only other option, that is a bit nicer, but not actually 
shorter is:

```d
import std.typecons : Yes, No;

void show_help_and_exit(const string exec_name, const string 
extra_help,
                         typeof(No.verbose) verbose = No.verbose,
                         typeof(No.reallyverbose) reallyverbose = 
No.reallyverbose,
                         typeof(No.shorthelp) shorthelp = 
No.shorthelp) {
...
}
```


Really, I would like to be able to say this:

```d
import std.typecons : Yes, No;

void show_help_and_exit(const string exec_name, const string 
extra_help,
                         auto verbose = No.verbose,
                         auto reallyverbose = No.reallyverbose,
                         auto shorthelp = No.shorthelp) {
...
}
```

(with possibly extra like `const`, `ref`, etc).


For a lot of stuff (like `Flag`), it is quite clear what is the 
type. And IDEs can also help here a lot.

I can't use the template here, because it would mess things up 
quite a bit, if the caller passes a wrong type, and because all 
the 3 above "flags" are own types, so this will not work:


```d
import std.typecons : Yes, No;

void show_help_and_exit(F)(const string exec_name, const string 
extra_help,
                            F verbose = No.verbose,
                            F reallyverbose = No.reallyverbose,
                            F shorthelp = No.shorthelp) {
...
}
```

And also templates will not work when using simple strategy for 
methods of classes or interfaces.


The `auto` would be also useful in other cases, for example, when 
using `Options` structs.

```d
struct Options {
   int a;
   string b;
   string c;
}

void run(string name, auto options = Options()) {
}
```

One, could even argue that this should apply not just to default 
argument (which can be handled using standard type inference), 
but all argument:

```d
auto f(auto x, auto y) {
   return g(x + y);
}
```

would be semi-equivalent to template like this:

```d
auto f(X, Y)(X x, Y y) {
   return g(x + y);
}
```

The only problem with that, is you would not be able to easily 
take address of such "function", or instantiate it explicitly to 
pass somewhere, so that probably is a bad idea long term.

But, for the normal type inference of the default arguments, 
everything else should work just fine. They can be functions, 
delegates, they have concrete type, can be taken address of, 
passed around, etc.

PS. Note that my "proposal" doesn't influence in anyway other 
discussions about the function arguments, like out-of-order named 
arguments, specifaying some out-of-order default arguments, etc.


There is not many other statically typed modern languages with 
type inference and this feature (I checked about 10 different 
ones). I found some tho.


First is "Crystal" (inspired by Ruby), 
https://crystal-lang.org/reference/syntax_and_semantics/type_inference.html#5-assigning-a-variable-that-is-a-method-parameter-with-a-default-value

```crystal
class Person
   def initialize(name = "John Doe")
     @name = name
   end
end
```

will infer locally `name` argument variable to be `String`, and 
so would be `@name` (member variable of the class).

It is also possible in Dart. Example:

```dart
String f(int a, {b = "bzium"}) {
   return '${a} - ${b}';
}

void main() {
   print(f(5));
   print(f.runtimeType.toString());  // (int, {dynamic b}) => 
String
}
```

It does work, but is not exactly equivalent to `String f(int a, 
{String b = "bzium"})`, because at the moment Dart compiler 
infers `b` to `dynamic`, instead of `String`, but it could be 
just deficiency of the compiler or specification on their side.

And Haxe ( 
https://haxe.org/manual/types-function-default-values.html ):


```haxe
   static function test(i = 12, s = "bar") {
     return "i: " + i + ", s: " + s;
   }

...

   $type(test);     // (?i : Int, ?s : String) -> String
```



More information about the Digitalmars-d mailing list