digitalmars.D - Type inference for default function / method arguments?
- Witold Baryluk (155/155) May 10 2021 Hi,
- Imperatorn (2/7) May 10 2021 You could maybe use Variant somehow?
- Witold Baryluk (2/11) May 10 2021 Could you elaborate? I am not sure how would it help in anyway.
- SealabJaster (16/18) May 10 2021 While not an exact solution, aliases can be a little useful here.
- Andrei Alexandrescu (4/17) May 11 2021 That's an antipattern. The very point of Flag is to allow avoiding
- rikki cattermole (3/22) May 11 2021 Once named arguments is implemented we can get rid of this crutch.
- SealabJaster (4/6) May 11 2021 Y... yea... who'd do such a thing...
- H. S. Teoh (12/21) May 10 2021 [...]
- Witold Baryluk (40/40) May 11 2021 Also OCaml supports inference for default arguments, but
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 ```
May 10 2021
On Monday, 10 May 2021 at 18:50:02 UTC, Witold Baryluk wrote: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.You could maybe use Variant somehow?
May 10 2021
On Monday, 10 May 2021 at 19:32:19 UTC, Imperatorn wrote:On Monday, 10 May 2021 at 18:50:02 UTC, Witold Baryluk wrote:Could you elaborate? I am not sure how would it help in anyway.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.You could maybe use Variant somehow?
May 10 2021
On Monday, 10 May 2021 at 18:50:02 UTC, Witold Baryluk wrote:Hi, using D for very long time.While not an exact solution, aliases can be a little useful here. ```d import std.typecons : Flag; // Shorten the names as much as you need of course. alias Verbose = Flag!"verbose"; alias VeryVerbose = Flag"veryVerbose"; void func(Verbose verbose = Verbose.yes, VeryVerbose veryVerbose = VeryVerbose.no) { } ``` And you can of course do that for other types, e.g. `alias Opt = Options` But again, this is more a workaround for why you're wanting the auto inference.
May 10 2021
On 5/10/21 4:01 PM, SealabJaster wrote:On Monday, 10 May 2021 at 18:50:02 UTC, Witold Baryluk wrote:That's an antipattern. The very point of Flag is to allow avoiding polluting the namespace and docs with such silly names. Similarly, you wouldn't define an alias for every Tuple you use.Hi, using D for very long time.While not an exact solution, aliases can be a little useful here. ```d import std.typecons : Flag; // Shorten the names as much as you need of course. alias Verbose = Flag!"verbose"; alias VeryVerbose = Flag"veryVerbose";
May 11 2021
On 12/05/2021 1:43 AM, Andrei Alexandrescu wrote:On 5/10/21 4:01 PM, SealabJaster wrote:Once named arguments is implemented we can get rid of this crutch. Binary Flag needs to go!On Monday, 10 May 2021 at 18:50:02 UTC, Witold Baryluk wrote:That's an antipattern. The very point of Flag is to allow avoiding polluting the namespace and docs with such silly names. Similarly, you wouldn't define an alias for every Tuple you use.Hi, using D for very long time.While not an exact solution, aliases can be a little useful here. ```d import std.typecons : Flag; // Shorten the names as much as you need of course. alias Verbose = Flag!"verbose"; alias VeryVerbose = Flag"veryVerbose";
May 11 2021
On Tuesday, 11 May 2021 at 13:43:26 UTC, Andrei Alexandrescu wrote:That's an antipattern. The very point of Flag is to allow avoiding polluting the namespace and docs with such silly names.Y... yea... who'd do such a thing... https://i.imgur.com/IFZbMRy.png
May 11 2021
On Mon, May 10, 2021 at 06:50:02PM +0000, Witold Baryluk via Digitalmars-d wrote: [...]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) {[...] I've also felt the need for this. Since the default value already implies the type, it seems to be a violation of DRY to have to type it out, especially for enums where you already have to type the name of the enum. IMO, this deserves to be a DIP. It's useful, self-contained, and probably quite easy to implement. T -- There are two ways to write error-free programs; only the third one works.
May 10 2021
Also OCaml supports inference for default arguments, but considering very powerful type inference solver in Ocaml, that is kind of cheating too. https://caml.inria.fr/pub/docs/u3-ocaml/ocaml051.html Example: ```ocaml let substring ?pos:(p=0) ~length:l s = String.sub s p l;; ``` Here, `p` is inferred to be `int`: ```ocaml val substring : ?pos:int -> length:int -> string -> string = <fun> ``` It inferred to be a for two reasons: default value (`0`) is an `int`, and `String.sub` second argument is supposed to be an `int`. However, it works even if the `p` is not used, and only the default value is used for initialization: ```ocaml let f ?x:(x=42) = x;; Warning 16: this optional argument cannot be erased. val f : ?x:int -> int = <fun> ``` `x` is inferred to be an `int`. This is different that, without default argument: ```ocaml Warning 16: this optional argument cannot be erased. val f : ?x:'a -> 'a option = <fun> ``` Where `f` is a generic function, working with any type. or ```ocaml let rec list_map f ?(accum = []) l = match l with | head :: tail -> list_map f ~accum:(f head :: accum) tail | [] -> List.rev accum;; val list_map : ('a -> 'b) -> ?accum:'b list -> 'a list -> 'b list = <fun> ``` accum is inferred to be a `list`. I think it is pretty handy, and brings D a bit closer to more compact format, know from places like Ocaml, Haskell, or dynamic languages like Python.
May 11 2021