digitalmars.D.learn - Is there a nice syntax to achieve optional named parameters?
- John Burton (22/22) Jan 15 2019 As an example let's say I have a type 'Window' that represents a
- rikki cattermole (7/7) Jan 15 2019 Longer term, you're better off with the builder. Even with named
- John Burton (6/9) Jan 15 2019 Thanks for your reply. But what is the builder?
- rikki cattermole (3/6) Jan 15 2019 https://en.wikipedia.org/wiki/Builder_pattern
- John Burton (5/12) Jan 15 2019 Ah right, that.
- Dukc (11/14) Jan 16 2019 You can make the constructor a template that takes a single
- Dukc (2/3) Jan 16 2019 meant "of arbitrary type"
- John Burton (7/21) Jan 16 2019 Thanks, I tried out the tuple approach and it works very well.
- JN (7/11) Jan 16 2019 You can slightly modify it to the way APIs like DirectX or Vulkan
- Kagamin (5/7) Jan 16 2019 In this particular case I would make the constructor take 3
- John Burton (12/18) Jan 17 2019 Well window was just an example really, my real use case is a
- H. S. Teoh (9/13) Jan 17 2019 [...]
- SrMordred (25/47) Jan 16 2019 Let me throw this idea here:
- John Burton (2/28) Jan 17 2019 Oh that's interesting!
- Matheus (14/21) Jan 17 2019 I usually do this too, I like to use struct and then in another
- SrMordred (7/13) Jan 17 2019 Yes, but there is a mistake there:
- Matheus (3/9) Jan 17 2019 I see now and thanks.
- kdevel (6/18) Jan 17 2019 [...]
- John Burton (6/14) Jan 18 2019 It likely is a bad idea for a small struct like this but if it
- evilrat (32/47) Jan 18 2019 You'd better profile and only then act.
- Kagamin (3/8) Jan 18 2019 For big Config struct it probably doesn't matter, but ref
- Neia Neutuladh (5/21) Jan 18 2019 Creating a window is the dominating cost here. If you're creating enough...
- Zenw (12/17) Jan 19 2019 how about this
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (37/55) Jan 20 2019 The problem with using string mixins like that is when you want
- John Burton (6/59) Jan 21 2019 Thanks everyone for your advice on this.
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
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
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
On 16/01/2019 1:05 AM, John Burton wrote:On Tuesday, 15 January 2019 at 11:26:50 UTC, rikki cattermole wrote:https://en.wikipedia.org/wiki/Builder_pattern One of the few OOP design patterns that I can agree with.Longer term, you're better off with the builder.Thanks for your reply. But what is the builder?
Jan 15 2019
On Tuesday, 15 January 2019 at 12:15:41 UTC, rikki cattermole wrote:On 16/01/2019 1:05 AM, John Burton wrote:Ah right, that. Thank you. Hmm that would work.On Tuesday, 15 January 2019 at 11:26:50 UTC, rikki cattermole wrote:https://en.wikipedia.org/wiki/Builder_pattern One of the few OOP design patterns that I can agree with.Longer term, you're better off with the builder.Thanks for your reply. But what is the builder?
Jan 15 2019
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
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
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: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.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
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
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
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: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.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 17 2019
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
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
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:Oh that's interesting![...]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 17 2019
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
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
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
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
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
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: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!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
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
On Fri, 18 Jan 2019 09:39:31 +0000, John Burton wrote:On Thursday, 17 January 2019 at 01:43:42 UTC, SrMordred wrote: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.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
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
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: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. -- SimenAs 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 20 2019
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: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.On Tuesday, 15 January 2019 at 11:14:54 UTC, John Burton wrote: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[...]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 21 2019