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