Jump to page: 1 2 3
Thread overview
A pattern I'd like to see more of - Parsing template parameter tuples
May 21, 2018
Ethan
May 21, 2018
Neia Neutuladh
May 21, 2018
Manu
May 21, 2018
Dmitry Olshansky
May 21, 2018
Paul Backus
May 21, 2018
Ethan
May 21, 2018
Ethan
May 21, 2018
rikki cattermole
May 21, 2018
crimaniak
May 21, 2018
Ethan
May 21, 2018
Jacob Carlborg
May 21, 2018
Jacob Carlborg
May 21, 2018
Sjoerd Nijboer
May 22, 2018
Ethan
May 22, 2018
Sjoerd Nijboer
May 22, 2018
Jonathan M Davis
May 22, 2018
Jacob Carlborg
May 22, 2018
Jonathan M Davis
May 22, 2018
jmh530
May 21, 2018
Code for context: https://github.com/GooberMan/binderoo/blob/master/binderoo_client/d/src/binderoo/util/enumoptions.d

Something struck me at DConf. I was watching the dxml talk and hearing about all these things that weren't being implemented for one reason or another. And I was thinking, "But what if I want those things?" Being D, it'd be pretty easy to opt in to them with template parameters and static if controlling what code gets executed at runtime.

But that brings up a bit of an annoying thing. Namely, the old school way of doing such things:

class SomeObject( bool option1, bool option2, Flags iHateBools = Flags.Default, int ohIDontWantThisToBeDefaultedButRefactoringSucks = -1 )
{
}

Pretty obnoxious design pattern.

But we're in D. We can do much better. It makes sense to do the following:

class SomeObject( LooseOptions... )
{
}

Much nicer. But how do we go about dealing with that? Static foreach each time we want something? One time parse and cache the values? Both are laborious in their own way. What we want is some helper objects to make sense of it all.

This is where my EnumOptions struct comes in. The idea here is that all the options you want as booleans, you put them in an enum like so:

enum SomeOptions
{
  Option1,
  Option2,
  Option5,
  Option3Sir,
  Option3
}

And then instantiate your class like so:

alias SomeInstantiatedObject = SomeObject!( SomeOptions.Option1, SomeOptions.Option2, SomeOptions.Option3 );

And inside your class definition, you clean it up automagically with a nice little helper function I made:

class SomeObject( LooseOptions... )
{
  enum Options = OptionsOf( SomeOptions, LooseOptions );
}

This resolves to an EnumOptions struct that parses all members of an enumeration, and generates bits in a bitfield for them and wraps it all up with properties. So now the following is possible:

static if( Options.Option1 )
{
  // Do the slow thing that I would like supported
}

Now, if you've been able to keep up here, you might have noticed something. Your class has a variable template parameter list. Which means we can throw anything in there. The plot thickens. This means you can go one step further and make your options actually human readable:

enum ObjectVersion
{
  _1_0,
  _1_1,
  _2_0,
}

enum ObjectEncoding
{
  UTF8,
  UTF16,
  UTF32,
  PlainASCII,
  ExtendedASCII,
}

class SomeDocument( Options... )
{
  enum Version = OptionsOf( ObjectVersion, Options );
  enum Encoding = OptionsOf( ObjectVersion, Options );
}

alias DocumentType = SomeDocument!( ObjectVersion._1_0, ObjectEncoding.PlainASCII );
alias DocumentType2 = SomeDocument!( ObjectEncoding.UTF8, ObjectVersion._2_0 );

Pretty, pretty, pretty good.

With this in the back of my mind, I've been able to expand Binderoo's module binding to be a bit more user friendly. I've got a new BindModules mixin, which unlike the existing mixins are more of a pull-in system rather than a push-in system. Basically, rather than BindModule at the bottom of each module, you put a single BindModules at the bottom of one module and list every module you want as a parameter to it.

The mixin needs to do a few things though. The list of modules is one thing. A bunch of behaviour options is another. And, since the mixin adds a static shared this, a list of functions that need to be executed for module initialisation. The first two are pretty easy to deal with:

enum Modules = ExtractAllOf!( string, Options );
enum BindOptions = OptionsOf!( BindOption, Options );

But the functions, they're a bit trickier. So I made a new trait in Binderoo's traits module called ExtractTupleOf. The template prototype is the following:

template ExtractTupleOf( alias TestTemplate, Symbols... )

That first parameter is the interesting one. It's essentially an uninstantiated template that doubles as a lambda. The template is expected to be an eponymous template aliasing to a boolean value, and take one parameter (although, theoretically, a CTFE bool function(T)() would also work). ExtractTupleOf will static foreach over each symbol in Symbols, and static if( TestTemplate!Symbol ) each one. If it returns true, then that symbol is extracted and put in a new tuple.

What does this mean? It means I can do this:

import std.traits : isSomeFunction;
mixin BindModuleStaticSetup!( ExtractTupleOf!( isSomeFunction, Options ) );

All of this is very definitely well in the real of "Let's see you do that in the hour it took me to throw it all together, C++!" territory. And I'd really like to see people pick up this pattern rather than emulate the old ways.
May 21, 2018
On Monday, 21 May 2018 at 00:13:26 UTC, Ethan wrote:
> Code for context: https://github.com/GooberMan/binderoo/blob/master/binderoo_client/d/src/binderoo/util/enumoptions.d

This looks good. One small caveat:

alias DocumentType = SomeDocument!(ObjectVersion._1_0, ObjectEncoding.UTF8);
alias DocumentType2 = SomeDocument!(ObjectEncoding.UTF8, ObjectVersion._1_0);

These are not the same type; they're two identical types with different mangles. You can fix that with a layer of indirection:

template SomeDocument(Options...)
{
  alias SomeDocument = SomeDocumentImpl!(OptionsOf!(DocumentParams, Options));
}
May 20, 2018
I don't really like that SomeObject() will be instantiated a crap load of times for every possible combination and order of options that a user might want to supply. How do you control the bloat in a way that people won't mess up frequently?

On 20 May 2018 at 17:58, Neia Neutuladh via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
> On Monday, 21 May 2018 at 00:13:26 UTC, Ethan wrote:
>>
>> Code for context: https://github.com/GooberMan/binderoo/blob/master/binderoo_client/d/src/binderoo/util/enumoptions.d
>
>
> This looks good. One small caveat:
>
> alias DocumentType = SomeDocument!(ObjectVersion._1_0, ObjectEncoding.UTF8);
> alias DocumentType2 = SomeDocument!(ObjectEncoding.UTF8,
> ObjectVersion._1_0);
>
> These are not the same type; they're two identical types with different mangles. You can fix that with a layer of indirection:
>
> template SomeDocument(Options...)
> {
>   alias SomeDocument = SomeDocumentImpl!(OptionsOf!(DocumentParams,
> Options));
> }
May 21, 2018
On Monday, 21 May 2018 at 00:13:26 UTC, Ethan wrote:
> But the functions, they're a bit trickier. So I made a new trait in Binderoo's traits module called ExtractTupleOf. The template prototype is the following:
>
> template ExtractTupleOf( alias TestTemplate, Symbols... )
>
> That first parameter is the interesting one. It's essentially an uninstantiated template that doubles as a lambda. The template is expected to be an eponymous template aliasing to a boolean value, and take one parameter (although, theoretically, a CTFE bool function(T)() would also work). ExtractTupleOf will static foreach over each symbol in Symbols, and static if( TestTemplate!Symbol ) each one. If it returns true, then that symbol is extracted and put in a new tuple.

Am I missing something, or is this the same thing as `std.meta: Filter`?
May 21, 2018
On Monday, 21 May 2018 at 01:53:20 UTC, Manu wrote:
> I don't really like that SomeObject() will be instantiated a crap load of times for every possible combination and order of options that a user might want to supply. How do you control the bloat in a way that people won't mess up frequently?

Just sort types by .stringof in a thin forwarding template, we have sort in std.meta now.

>
> On 20 May 2018 at 17:58, Neia Neutuladh via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>> [...]

May 21, 2018
On 21/05/2018 12:13 PM, Ethan wrote:
> Code for context: https://github.com/GooberMan/binderoo/blob/master/binderoo_client/d/src/binderoo/util/enumoptions.d 
> 
> 
> Something struck me at DConf. I was watching the dxml talk and hearing about all these things that weren't being implemented for one reason or another. And I was thinking, "But what if I want those things?" Being D, it'd be pretty easy to opt in to them with template parameters and static if controlling what code gets executed at runtime.
> 
> But that brings up a bit of an annoying thing. Namely, the old school way of doing such things:
> 
> class SomeObject( bool option1, bool option2, Flags iHateBools = Flags.Default, int ohIDontWantThisToBeDefaultedButRefactoringSucks = -1 )
> {
> }
> 
> Pretty obnoxious design pattern.
> 
> But we're in D. We can do much better. It makes sense to do the following:
> 
> class SomeObject( LooseOptions... )
> {
> }
> 
> Much nicer. But how do we go about dealing with that? Static foreach each time we want something? One time parse and cache the values? Both are laborious in their own way. What we want is some helper objects to make sense of it all.
> 
> This is where my EnumOptions struct comes in. The idea here is that all the options you want as booleans, you put them in an enum like so:
> 
> enum SomeOptions
> {
>    Option1,
>    Option2,
>    Option5,
>    Option3Sir,
>    Option3
> }
> 
> And then instantiate your class like so:
> 
> alias SomeInstantiatedObject = SomeObject!( SomeOptions.Option1, SomeOptions.Option2, SomeOptions.Option3 );
> 
> And inside your class definition, you clean it up automagically with a nice little helper function I made:
> 
> class SomeObject( LooseOptions... )
> {
>    enum Options = OptionsOf( SomeOptions, LooseOptions );
> }
> 
> This resolves to an EnumOptions struct that parses all members of an enumeration, and generates bits in a bitfield for them and wraps it all up with properties. So now the following is possible:
> 
> static if( Options.Option1 )
> {
>    // Do the slow thing that I would like supported
> }
> 
> Now, if you've been able to keep up here, you might have noticed something. Your class has a variable template parameter list. Which means we can throw anything in there. The plot thickens. This means you can go one step further and make your options actually human readable:
> 
> enum ObjectVersion
> {
>    _1_0,
>    _1_1,
>    _2_0,
> }
> 
> enum ObjectEncoding
> {
>    UTF8,
>    UTF16,
>    UTF32,
>    PlainASCII,
>    ExtendedASCII,
> }
> 
> class SomeDocument( Options... )
> {
>    enum Version = OptionsOf( ObjectVersion, Options );
>    enum Encoding = OptionsOf( ObjectVersion, Options );
> }
> 
> alias DocumentType = SomeDocument!( ObjectVersion._1_0, ObjectEncoding.PlainASCII );
> alias DocumentType2 = SomeDocument!( ObjectEncoding.UTF8, ObjectVersion._2_0 );
> 
> Pretty, pretty, pretty good.
> 
> With this in the back of my mind, I've been able to expand Binderoo's module binding to be a bit more user friendly. I've got a new BindModules mixin, which unlike the existing mixins are more of a pull-in system rather than a push-in system. Basically, rather than BindModule at the bottom of each module, you put a single BindModules at the bottom of one module and list every module you want as a parameter to it.
> 
> The mixin needs to do a few things though. The list of modules is one thing. A bunch of behaviour options is another. And, since the mixin adds a static shared this, a list of functions that need to be executed for module initialisation. The first two are pretty easy to deal with:
> 
> enum Modules = ExtractAllOf!( string, Options );
> enum BindOptions = OptionsOf!( BindOption, Options );
> 
> But the functions, they're a bit trickier. So I made a new trait in Binderoo's traits module called ExtractTupleOf. The template prototype is the following:
> 
> template ExtractTupleOf( alias TestTemplate, Symbols... )
> 
> That first parameter is the interesting one. It's essentially an uninstantiated template that doubles as a lambda. The template is expected to be an eponymous template aliasing to a boolean value, and take one parameter (although, theoretically, a CTFE bool function(T)() would also work). ExtractTupleOf will static foreach over each symbol in Symbols, and static if( TestTemplate!Symbol ) each one. If it returns true, then that symbol is extracted and put in a new tuple.
> 
> What does this mean? It means I can do this:
> 
> import std.traits : isSomeFunction;
> mixin BindModuleStaticSetup!( ExtractTupleOf!( isSomeFunction, Options ) );
> 
> All of this is very definitely well in the real of "Let's see you do that in the hour it took me to throw it all together, C++!" territory. And I'd really like to see people pick up this pattern rather than emulate the old ways.

Another option[0] ;)

[0] https://github.com/rikkimax/DIPs/blob/named_args/DIPs/DIP1xxx-RC.md
May 21, 2018
On Monday, 21 May 2018 at 03:30:37 UTC, Paul Backus wrote:
> Am I missing something, or is this the same thing as `std.meta: Filter`?

Nope, I am missing something.

I don't find the std library documentation anywhere near as easy to look through as something like cppreference.com, so I only tend to go there when I know what I'm looking for.

And in this case, I know the std.meta containers are implemented using recursive templates. Which is not particularly nice on the compiler. The difference with mine (apart from the name mismatch) is that I use string mixins and iterate over items in a for loop, which I've done with quite a few traits to try and get compile times a bit more reasonable on larger codebases.

Otherwise, the usage technique is exactly the same.
May 21, 2018
On Monday, 21 May 2018 at 01:53:20 UTC, Manu wrote:
> I don't really like that SomeObject() will be instantiated a crap load of times for every possible combination and order of options that a user might want to supply. How do you control the bloat in a way that people won't mess up frequently?

On Monday, 21 May 2018 at 00:58:10 UTC, Neia Neutuladh wrote:
> These are not the same type; they're two identical types with different mangles.

Yep, entirely something I missed when I did the code. Classic "Works For Me!(TM)" moment.

As mentioned by Neia, the thin wrapper would be my preferred way of approaching that, at which point your implementation class looks like:

class Impl( alias Version, alias Encoding )

There's nothing stopping you doing the proposed std.meta.sort either, but that's essentially doubling up on work when you do something like what I illustrated.
May 21, 2018
On 5/21/18 5:30 AM, Ethan wrote:
> On Monday, 21 May 2018 at 03:30:37 UTC, Paul Backus wrote:
>> Am I missing something, or is this the same thing as `std.meta: Filter`?
> 
> Nope, I am missing something.
> 
> I don't find the std library documentation anywhere near as easy to look through as something like cppreference.com, so I only tend to go there when I know what I'm looking for.
> 
> And in this case, I know the std.meta containers are implemented using recursive templates. Which is not particularly nice on the compiler. The difference with mine (apart from the name mismatch) is that I use string mixins and iterate over items in a for loop, which I've done with quite a few traits to try and get compile times a bit more reasonable on larger codebases.

Filter was written before static foreach existed. This is a pretty low-hanging fruit if anyone wants to try it out.

-Steve
May 21, 2018
On Monday, 21 May 2018 at 13:22:33 UTC, Steven Schveighoffer wrote:
> Filter was written before static foreach existed. This is a pretty low-hanging fruit if anyone wants to try it out.
>
> -Steve

I've gone to the effort after all, I might as well just port my code across. I'll look in to it. Probably not this week though, off to Copenhagen/Malmö in a few hours.
« First   ‹ Prev
1 2 3