www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Is there a nice syntax to achieve optional named parameters?

reply John Burton <john.burton jbmail.com> writes:
As an example let's say I have a type 'Window' that represents a 
win32 window. I'd like to be able to construct an instance of the 
type with some optional parameters that default to some 
reasonable settings and create the underlying win32 window.

I'd ideally like some syntax like this :-

auto window = Window(title = "My Window", width = 1000, 
fullscreen = true);

Assume that title, width, fullscreen are optional and if not 
specified there are defaults to use. And that there are many 
other settings than just these 3 that I've chosen to just use the 
default here.

I know that I can't do it like this is D but what is the best way 
to achieve this kind of thing? I can add properties and then do a 
specific "create" function to create the underlying win32 window 
once I'm done but that seems ugly.

auto window = Window();
window.title = "My Window";
window.width = 1000;
window.create();

This is ok, but I'm not so keen on separating the creation and 
construction like this.
Is there a better way that's not ugly?
Jan 15 2019
next sibling parent reply rikki cattermole <rikki cattermole.co.nz> writes:
Longer term, you're better off with the builder. Even with named 
parameters (2 DIP's are in the queue for adding it).

Creating windows is a very complex task that can balloon in scope.
Being able to hide it away in a separate type can be quite desirable if 
you want your windowing library to be actually useful in not just one 
simple case.

Of course that doesn't mean you couldn't hide that its there ;)
Jan 15 2019
parent reply John Burton <john.burton jbmail.com> writes:
On Tuesday, 15 January 2019 at 11:26:50 UTC, rikki cattermole 
wrote:
 Longer term, you're better off with the builder.
Thanks for your reply. But what is the builder?
 Creating windows is a very complex task that can balloon in 
 scope.
Well that was mostly just an example that I thought people could relate to more than my obscure real case :) But the principle is the same so the answer is likely too
Jan 15 2019
parent reply rikki cattermole <rikki cattermole.co.nz> writes:
On 16/01/2019 1:05 AM, John Burton wrote:
 On Tuesday, 15 January 2019 at 11:26:50 UTC, rikki cattermole wrote:
 Longer term, you're better off with the builder.
Thanks for your reply. But what is the builder?
https://en.wikipedia.org/wiki/Builder_pattern One of the few OOP design patterns that I can agree with.
Jan 15 2019
parent John Burton <john.burton jbmail.com> writes:
On Tuesday, 15 January 2019 at 12:15:41 UTC, rikki cattermole 
wrote:
 On 16/01/2019 1:05 AM, John Burton wrote:
 On Tuesday, 15 January 2019 at 11:26:50 UTC, rikki cattermole 
 wrote:
 Longer term, you're better off with the builder.
Thanks for your reply. But what is the builder?
https://en.wikipedia.org/wiki/Builder_pattern One of the few OOP design patterns that I can agree with.
Ah right, that. Thank you. Hmm that would work.
Jan 15 2019
prev sibling next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Tuesday, 15 January 2019 at 11:14:54 UTC, John Burton wrote:
 This is ok, but I'm not so keen on separating the creation and 
 construction like this.
 Is there a better way that's not ugly?
You can make the constructor a template that takes a single struct of arbitrary, and inspects (at compile time) if it has fields with certain names and types. Then, when constructing, you feed that constructor a std.typecons.Tuple with named fields. Or alternatively, use a separate builder type that makes a good struct to feed for the window constructor. The disadvantage is that you cannot link the constructor template directly for external programs. But for that, you define some sort of wrapper function that always takes all the parameters and then calls the template.
Jan 16 2019
next sibling parent Dukc <ajieskola gmail.com> writes:
On Wednesday, 16 January 2019 at 11:21:53 UTC, Dukc wrote:
 a template that takes a single struct of arbitrary,
meant "of arbitrary type"
Jan 16 2019
prev sibling parent John Burton <john.burton jbmail.com> writes:
On Wednesday, 16 January 2019 at 11:21:53 UTC, Dukc wrote:
 On Tuesday, 15 January 2019 at 11:14:54 UTC, John Burton wrote:
 This is ok, but I'm not so keen on separating the creation and 
 construction like this.
 Is there a better way that's not ugly?
You can make the constructor a template that takes a single struct of arbitrary, and inspects (at compile time) if it has fields with certain names and types. Then, when constructing, you feed that constructor a std.typecons.Tuple with named fields. Or alternatively, use a separate builder type that makes a good struct to feed for the window constructor. The disadvantage is that you cannot link the constructor template directly for external programs. But for that, you define some sort of wrapper function that always takes all the parameters and then calls the template.
Thanks, I tried out the tuple approach and it works very well. Constructing a tuple at the point of call with named fields works well, but looks a bit "ugly" to me but I might use it. I think on balance that creating a separate builder struct that I can set the fields in and pass to the "real" constructor might be the way to go though for me.
Jan 16 2019
prev sibling next sibling parent JN <666total wp.pl> writes:
On Tuesday, 15 January 2019 at 11:14:54 UTC, John Burton wrote:
 auto window = Window();
 window.title = "My Window";
 window.width = 1000;
 window.create();
You can slightly modify it to the way APIs like DirectX or Vulkan do it. auto windowinfo = WindowInfo(); windowinfo.title = "My Window"; windowinfo.width = 1000; auto window = Window(windowinfo);
Jan 16 2019
prev sibling next sibling parent reply Kagamin <spam here.lot> writes:
On Tuesday, 15 January 2019 at 11:14:54 UTC, John Burton wrote:
 auto window = Window(title = "My Window", width = 1000, 
 fullscreen = true);
In this particular case I would make the constructor take 3 parameters - title, width and height. Full screen is a rare functionality and shouldn't clutter the constructor, can it be set after the window is created?
Jan 16 2019
parent reply John Burton <john.burton jbmail.com> writes:
On Wednesday, 16 January 2019 at 14:59:01 UTC, Kagamin wrote:> On 
Tuesday, 15 January 2019 at 11:14:54 UTC, John Burton wrote:
 auto window = Window(title = "My Window", width = 1000, 
 fullscreen = true);
In this particular case I would make the constructor take 3 parameters - title, width and height. Full screen is a rare functionality and shouldn't clutter the constructor, can it be set after the window is created?
Well window was just an example really, my real use case is a similar object that needs a lot of configuration where mostly the default works but you might want to override, and the config is needed to create the object in the first place. For this example you are right though, and it may be that I'm overthinking the whole thing.
Jan 17 2019
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Thu, Jan 17, 2019 at 10:29:13AM +0000, John Burton via Digitalmars-d-learn
wrote:
[...]
 Well window was just an example really, my real use case is a similar
 object that needs a lot of configuration where mostly the default
 works but you might want to override, and the config is needed to
 create the object in the first place.
[...] When I encounter similar situations in my code, my go-to solution is to use a struct with default field values as a configuration object that you pass to the ctor. You can either pass .init to the ctor to get default behavior, or declare an instance of the struct and customize as you see fit before handing it to the ctor. --T
Jan 17 2019
prev sibling next sibling parent reply SrMordred <patric.dexheimer gmail.com> writes:
On Tuesday, 15 January 2019 at 11:14:54 UTC, John Burton wrote:
 As an example let's say I have a type 'Window' that represents 
 a win32 window. I'd like to be able to construct an instance of 
 the type with some optional parameters that default to some 
 reasonable settings and create the underlying win32 window.

 I'd ideally like some syntax like this :-

 auto window = Window(title = "My Window", width = 1000, 
 fullscreen = true);

 Assume that title, width, fullscreen are optional and if not 
 specified there are defaults to use. And that there are many 
 other settings than just these 3 that I've chosen to just use 
 the default here.

 I know that I can't do it like this is D but what is the best 
 way to achieve this kind of thing? I can add properties and 
 then do a specific "create" function to create the underlying 
 win32 window once I'm done but that seems ugly.

 auto window = Window();
 window.title = "My Window";
 window.width = 1000;
 window.create();

 This is ok, but I'm not so keen on separating the creation and 
 construction like this.
 Is there a better way that's not ugly?
Let me throw this idea here: struct Config { string title; int width; } struct Window { this(Config config) { //use static foreach magic to set everything :P } } auto NewWindow( alias code )() { mixin("Config config = {"~code~"};"); return Window(config); } //usage: auto a = NewWindow!q{ title : "MainTitle" }; auto b = NewWindow!q{ title : "MainTitle", width : 800 }; auto c = NewWindow!q{ width : 1000 }; auto d = NewWindow!q{}; :)
Jan 16 2019
next sibling parent John Burton <john.burton jbmail.com> writes:
On Thursday, 17 January 2019 at 01:43:42 UTC, SrMordred wrote:
 On Tuesday, 15 January 2019 at 11:14:54 UTC, John Burton wrote:
 [...]
Let me throw this idea here: struct Config { string title; int width; } struct Window { this(Config config) { //use static foreach magic to set everything :P } } auto NewWindow( alias code )() { mixin("Config config = {"~code~"};"); return Window(config); } //usage: auto a = NewWindow!q{ title : "MainTitle" }; auto b = NewWindow!q{ title : "MainTitle", width : 800 }; auto c = NewWindow!q{ width : 1000 }; auto d = NewWindow!q{}; :)
Oh that's interesting!
Jan 17 2019
prev sibling next sibling parent reply Matheus <stop spam.com> writes:
On Thursday, 17 January 2019 at 01:43:42 UTC, SrMordred wrote:
 Let me throw this idea here:
...
I usually do this too, I like to use struct and then in another language I use reflection do optimize binding. Anyway I understood all your code, except for this "alias code"
 auto NewWindow( alias code )()
 {
 	mixin("Config config = {"~code~"};");
 	return Window(config);
 }
Looking on specs: https://dlang.org/spec/declaration.html#alias "AliasDeclarations create a symbol that is an alias for another type, and can be used anywhere that other type may appear." So with your example imagine this: foo(alias x){} foo("a"); foo(1); 'x' will be string one time and integer another? Or there is something that I'm missing. Matheus.
Jan 17 2019
parent reply SrMordred <patric.dexheimer gmail.com> writes:
On Thursday, 17 January 2019 at 12:11:02 UTC, Matheus wrote:
 foo(alias x){}

 foo("a");
 foo(1);

 'x' will be string one time and integer another? Or there is 
 something that I'm missing.

 Matheus.
Yes, but there is a mistake there: alias is part of the template: foo(alias x)(){} //note extra parens than u call like an template: foo!"a"; //equivalent = foo!("a")(); foo!1;
Jan 17 2019
parent Matheus <stop spam.com> writes:
On Thursday, 17 January 2019 at 16:55:33 UTC, SrMordred wrote:
 Yes, but there is a mistake there:
 alias is part of the template:

 foo(alias x)(){} //note extra parens

 than u call like an template:

 foo!"a"; //equivalent = foo!("a")();
 foo!1;
I see now and thanks. Matheus.
Jan 17 2019
prev sibling next sibling parent kdevel <kdevel vogtner.de> writes:
On Thursday, 17 January 2019 at 01:43:42 UTC, SrMordred wrote:
 On Tuesday, 15 January 2019 at 11:14:54 UTC, John Burton wrote:
[...]
 auto window = Window();
 window.title = "My Window";
 window.width = 1000;
 window.create();
[...]
 Is there a better way that's not ugly?
[...]
 //usage:
 auto a = NewWindow!q{ title : "MainTitle" };
 auto b = NewWindow!q{ title : "MainTitle", width : 800 };
 auto c = NewWindow!q{ width : 1000 };
 auto d = NewWindow!q{};


 :)
Put a semicolon instead of the comma in the line "auto b ..." and compile.
Jan 17 2019
prev sibling parent reply John Burton <john.burton jbmail.com> writes:
On Thursday, 17 January 2019 at 01:43:42 UTC, SrMordred wrote:

 struct Config
 {
 	string title;
 	int width;
 }

 struct Window
 {
 	this(Config config)
It likely is a bad idea for a small struct like this but if it was much bigger would it makes sense to write this as :- this(const ref Config config) Which is what you might do in C++ or does D handle this differently?
Jan 18 2019
next sibling parent evilrat <evilrat666 gmail.com> writes:
On Friday, 18 January 2019 at 09:39:31 UTC, John Burton wrote:
 On Thursday, 17 January 2019 at 01:43:42 UTC, SrMordred wrote:

 struct Config
 {
 	string title;
 	int width;
 }

 struct Window
 {
 	this(Config config)
It likely is a bad idea for a small struct like this but if it was much bigger would it makes sense to write this as :- this(const ref Config config) Which is what you might do in C++ or does D handle this differently?
You'd better profile and only then act. Seriously, whatever you know and familiar with in C++ might not work with D, you'll just end up with C++'ish code that just happens to be written in D. D is not C++, and such premature optimizations might cause more harm if applied on occassion or out of habit. IIRC D structs are all movable, so doing const refs here and there or using writing to ref parameter instead normal return(RVO) is likely bad for your code. D has its own ABI, it's not even compatible with C++, so when you pass structs by value it may or may not produce similar asm as C++ compilers does. When struct contains strings/arrays they are anyway managed by GC(unless you allocated it on your own), but this is something that more seasoned D users can explain in better details and cleaner explanation than me, if it means anything at all. Even without all this, do you really want to mess up your codebase with implementation details about how it should do it, or write clean (self)documented code? Is it really going to be a bottleneck in your program? Passing one big struct at widget's creation on program startup and some rare events really going to kill performance? Why not just write working code first and then identify bottlenecks and optimize when it absolutely ultimately necessary? Btw I don't remember much ref parameters in phobos, just look at it... I found only 4 ref consts it in std.file, 3 in std.array, other modules more likely to have them in comparison operators(I have no idea why, to not introduce potential RVO case?) and in unit tests for testing correct behavior. Again, D is not C++, what might work or even considered "good practice" there may or may not work here. So just profile it!
Jan 18 2019
prev sibling next sibling parent Kagamin <spam here.lot> writes:
On Friday, 18 January 2019 at 09:39:31 UTC, John Burton wrote:
 It likely is a bad idea for a small struct like this but if it 
 was much bigger would it makes sense to write this as :-

 	this(const ref Config config)

 Which is what you might do in C++ or does D handle this 
 differently?
For big Config struct it probably doesn't matter, but ref parameters don't accept rvalues.
Jan 18 2019
prev sibling parent Neia Neutuladh <neia ikeran.org> writes:
On Fri, 18 Jan 2019 09:39:31 +0000, John Burton wrote:
 On Thursday, 17 January 2019 at 01:43:42 UTC, SrMordred wrote:
 
 struct Config {
 	string title;
 	int width;
 }

 struct Window {
 	this(Config config)
It likely is a bad idea for a small struct like this but if it was much bigger would it makes sense to write this as :- this(const ref Config config) Which is what you might do in C++ or does D handle this differently?
Creating a window is the dominating cost here. If you're creating enough windows that copying them is a problem, you're doing something seriously weird and it might be more productive to step back and think about that than to switch to const ref.
Jan 18 2019
prev sibling parent reply Zenw <zero.error.no.warning gmail.com> writes:
On Tuesday, 15 January 2019 at 11:14:54 UTC, John Burton wrote:
 As an example let's say I have a type 'Window' that represents 
 a win32 window. I'd like to be able to construct an instance of 
 the type with some optional parameters that default to some 
 reasonable settings and create the underlying win32 window.

 [...]
how about this auto With(string code,T)(T value) { with(value) { mixin(code ~";"); } return value; } auto window = Window().With!q{title = "My window",width = 800,fullscreen = true};
Jan 19 2019
parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Saturday, 19 January 2019 at 14:26:31 UTC, Zenw wrote:
 On Tuesday, 15 January 2019 at 11:14:54 UTC, John Burton wrote:
 As an example let's say I have a type 'Window' that represents 
 a win32 window. I'd like to be able to construct an instance 
 of the type with some optional parameters that default to some 
 reasonable settings and create the underlying win32 window.

 [...]
how about this auto With(string code,T)(T value) { with(value) { mixin(code ~";"); } return value; } auto window = Window().With!q{title = "My window",width = 800,fullscreen = true};
The problem with using string mixins like that is when you want to use some local variable: int width = getWidth(); auto window = Window().With!q{width = width}; This would work: struct Window { string title; int width; bool fullscreen; } auto With(T, Args...)(T ctx, Args args) { static foreach (i; 0..Args.length) { mixin("ctx."~Args[i].name~" = args[i].value;"); } return ctx; } struct args { static opDispatch(string _name, T)(T value) { struct Result { enum name = _name; T value; } return Result(value); } } unittest { auto window = Window().With(args.title = "My window", args.width = 800, args.fullscreen = true); assert(window.title == "My window"); assert(window.width == 800); assert(window.fullscreen == true); } However, I don't see that there's all that much gain compared to just assigning the fields the normal way. -- Simen
Jan 20 2019
parent John Burton <john.burton jbmail.com> writes:
On Monday, 21 January 2019 at 07:57:58 UTC, Simen Kjærås wrote:
 On Saturday, 19 January 2019 at 14:26:31 UTC, Zenw wrote:
 On Tuesday, 15 January 2019 at 11:14:54 UTC, John Burton wrote:
 [...]
how about this auto With(string code,T)(T value) { with(value) { mixin(code ~";"); } return value; } auto window = Window().With!q{title = "My window",width = 800,fullscreen = true};
The problem with using string mixins like that is when you want to use some local variable: int width = getWidth(); auto window = Window().With!q{width = width}; This would work: struct Window { string title; int width; bool fullscreen; } auto With(T, Args...)(T ctx, Args args) { static foreach (i; 0..Args.length) { mixin("ctx."~Args[i].name~" = args[i].value;"); } return ctx; } struct args { static opDispatch(string _name, T)(T value) { struct Result { enum name = _name; T value; } return Result(value); } } unittest { auto window = Window().With(args.title = "My window", args.width = 800, args.fullscreen = true); assert(window.title == "My window"); assert(window.width == 800); assert(window.fullscreen == true); } However, I don't see that there's all that much gain compared to just assigning the fields the normal way. -- Simen
Thanks everyone for your advice on this. I'm decided to just use a parameter structure to pass in. Works well and everyone can understand it. The question was really just does D offer a nicer way and the answer appears to be "not really". But the original approach isn't bad anyway.
Jan 21 2019