A pattern I'd like to see more of - Parsing template parameter tuples

Ethan gooberman at gmail.com
Mon May 21 00:13:26 UTC 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.


More information about the Digitalmars-d mailing list