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