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:
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:
// 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:
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:
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:
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.
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:
auto f(auto x, auto y) {
return g(x + y);
}
would be semi-equivalent to template like this:
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
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:
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 ):
static function test(i = 12, s = "bar") {
return "i: " + i + ", s: " + s;
}
...
$type(test); // (?i : Int, ?s : String) -> String