Hello! It's a bit long one, I guess, but I'd like to have some discussion of topic. I'll start with a concrete use case:

For the sake of entertainment, I tried to wrote generic configuration management class. I was inspired by oslo_config python package that I have to deal with on work. I started with:

# module config

class Config
{
    this(string filename) { ... }
    void save() abstract;
    void load() abstract;
    // Some implementation, json for example
}

abstract class ConfigGroup(string group_name_par)
{
    static immutable group_name = group_name_par;
    protected Config CONF;
    this(Config config) { CONF = config; }

    mixin template ConfigField(T, string opt_name)
        {
                mixin("@property " ~ T.stringof ~ " " ~ opt_name ~
"() { return CONF.root[\"" ~ group_name ~ "\"][\"" ~ opt_name ~
                          "\"]." ~ json_type(T.stringof) ~ "; }");
                mixin("@property " ~ T.stringof ~ " " ~ opt_name ~
"(" ~ T.stringof ~ " value) { return CONF.root[\"" ~ group_name ~ "\"][\"" ~ opt_name ~ "\"]." ~ json_type(T.stringof) ~ "(value); }");
        }
}

# module testconfig

private class TestConfigGroup: ConfigGroup!("testGroup")
{
        this(Config config) { super(config); }

        mixin ConfigField!(string, "some_string_option");
        mixin ConfigField!(double, "some_double_option");
}


... aand I stopped. And here are the blockers I saw:

1). I had to save template parameter group_name_par into group_name. Looks like template mixin doesn't support closures. A minor inconvenience, I would say, and it's not what I would like to talk about. 2). After preprocessing I wish to have fully-typed, safe and fast Config class, that contains all the groups I defined for it in it's body, and not as references. I don't want pointer lookup during runtime to get some field. This is actually quite a problem for D: 2.1). Looks like mixin is the only instrument to extend class body. Obvious solution would be a loop that mixins some definitions from some compile-time known array, meybe even string array. And the pretties of all ways - so that such array will contain module names and class names of all ConfigGroup derivatives defined in whole program (absolute madman). Said array could be appended in compile-time by every derivative of ConfigGroup. 2.2) Sweet dream of 2.1 is met with absence of tools to create and manipulate state during preprocessing. For example:

immutable string[] primordial = []; // maybe some special qualifier instead // of immutable will be better. Even // better if it shifts to immutable
                                         // during run-time
premixin template (string toAdd) { primordial ~= toAdd; } // for example
    mixin template Populate
    {
        foreach (s; primordial)
            mixin("int " ~ s);    // create some int field
    }
    class Populated { mixin Populate; }
    // another module
    premixin("field1")  // evaluated in preprocessor in
    premixin("field2")  // order of definition
    Populated p = new Populated;
    p.field1 = 3;

By "premixin" I mean that all such operations are performed in our special preprocessor stage, that is completed before mixins we already have now start to do their jobs.

2.3) There is strong C ancestry in D. The one regarding compilation being performed on translation units (.d source files) is, in my opinion, quite devastating. I don't know about you guys, but in 2017 I compile programs. I don't care about individual object files and linker shenanigans, for me it's the whole program that matters, and object files are just the way C does it's thing. You definetly must respect it while interfacing with it, but that's about it. Correct me if I'm wrong, but departure from C's compile process (CLI is not the cause here I believe) allowed C# to encorporate "partial" classes, wich are a wonderfull concept - class can be extended only volunteeringly (like in D, where we need to willingly write mixin to change definition), and localized source code changes: when project functionality is extended, old code base can sometimes remain completely untouched (this is huge for very big projects IMO). I will not deny, however, that readability of such code suffers. As a counter-argument, relationships between portion of the class and other code are usually local, in a way that this class part's fields are used by source code in this folder and basically nowhere else. But what's done is done, I understand. However, I believe, preprocessor still has hope for it, and can be generalized to whole source tree without throwing old toolchain out of the window. In the way that would allow "primordial" string array from the example above to be the same for all translation units after preprocessing is done. 2.4) Original configuration management example would also require the ability to import definitions cyclically. Module A containing ConfigGroupConcrete instantiation imports module B where Config is defined, wich will require B to import A in order to access ConfigGroupConcrete definition. Yet another stone in C's garden, yes. You could, for example, pass whole ConfigGroupConcrete body as a string and mixin it there, but then you would require to automatically build such string, and at this point you're better off with some kind of templating language. And templating languages make thing even less readable IMO, while simply being a crutches to replace language preprocessors, that don't follow industry needs. I do believe such case is out of reach until preprocessing is done on whole program united.


To conclude, I'll summarize my questions:
1). Is there a compiled language that is capable of the abovementiond tricks, without resorting to external templating meta-languages? 2). How deep the rabbit hole goes in terms of complexity of preprocessor modifications required? And for DMD in general?
3). How are cyclic module imports handled currently in D?
4). Is there hope that it's possible to do in, say, a year? I don't mind trying to implement it myself, but I don't want to invest time in thing that is so conceptually out of plane that will simply be too destructive for current compiler environment.

Reply via email to