On Thursday 23 December 2010 01:11:40 Mariusz Gliwiński wrote:
> Hello,
> I've been trying to manage this on my own for like 2 days but still
> couldn't do that, and because my brain just suddenly turned-off, I would
> ask You for some guidance.
> 
> The thing is:
> I'd like to make some kind of messaging in my application. So, there is
> - interface Msg
> - classes that are implementing it, i.e. MsgWindowClose
> - interface Provider for message publishers, i.e. MsgProviderWindowClose
> - interface Listener for message subscibers i.e. MsgListenerWindowClose
> - interface Handler for message filters i.e. bool
> MsgHandlerWindowClose.log() - interface Mediator for message passing
> between program classes, i.e. MsgMediatorDMultiThreading() - which is
> using D messages btw.
> 
> Nothing related to D so far. Key thing is, that most of this Msg objects
> will be really generic, so I'm building object generator, i.e
> MsgProviderGen!"WindowClose":
> <code>
> template MsgProviderGen(string MSG, Args...) {
> 
>       const char[] MsgProviderGen = "class MsgProvider"~MSG~" : MsgProvider {"
> 
>                       ~"override Msg"~MSG~" getMsg() {"
>                               ~"return new Msg"~MSG~";"
>                       ~"}"
> 
>               ~"}";
> 
> }
> </code>
> Which will be bigger of course.
> 
> Then, for standard and generic messages i can easily define:
> <code>
> private import
>       msg.msg,
>       msg.provider.gen,
>       msg.handler.gen;
> 
> class MsgWindowClose : Msg {
> 
> }
> mixin(MsgProviderGen!"WindowClose");
> </code>
> 
> 
> So far so good, but then if I'd like to add
> <code>mixin("immutable uint id="~static_id~";")</code>
> for each *class* (not object) I got a
> 
> PROBLEM:
> Each compile-time variable has to be const, right? So I can't increment my
> `static_id` each time I build a class (how many times template has
> generated provider/listener of this type). This wont let me statically
> check if each listener has its provider.
> 
> Surely it's not the only use of this feature, For loosen coupling, I'd wish
> to add function that statically returns array of Msg defined in module. It
> isn't an option without compile-time variables too.
> 
> Is it something about undefined module initialization?
> Or maybe there is any way to overcome this problems, since I'm still new @
> D language.
> 
> Ps. How to make associative array of dynamic arrays?
> <code>MsgHandler[hash_t][] _handlers = new MsgHandler[hash_t][];</code>
> wont work.

You're mixing up several concepts, which complicates things a fair bit.

When directly initializing static variables or member variables, the values 
used 
to initialize those variables must not depend on ordering. That generally means 
that the values used to initialize such variables must either come from 
constants, templates, or CTFE. So,

auto a = 7;
auto b = a;

is not legal, because a is not constant. If it were

immutable a = 7;
auto b = a;

then it's legal. However, note that b itself is not const, immutable, or an 
enum. It's fully mutable. Other ways to initialize such variables would be 
through eponymous templates and calling functions via CTFE (compile-time 
function evaluation):

template add(int a, int b)
{
    enum add = a + b;
}

double multiply(double a, double b)
{
    return a * b;
}

auto a = add!(7, 5);
auto b = multiply(2, 3);

Enforcing the lack of ordering in direct initialization for static variables 
and 
member variables makes it so that you avoid bugs like you get in some languages 
when you try to do something like

auto a = b;
auto b = 7;

and a ends up being garbage because the variables are being initialized in 
order 
and b hasn't been initialized yet. It also makes it potentially much faster to 
compile static and member variables, because they can actually be compiled in 
parallel instead of enforcing an ordering to them. And, of course, if you did 
something like

auto a = b;
auto b = a;

the circular dependency would screw you. If you have such ordering issues or if 
you need such variables to be mutable and yet able be initialized from one 
another, you need to use static constructors (which also work on immutable 
variables as long as you initialize them only once, though enums must still be 
known at compile time, so it doesn't work for them).


Now, for templates, the template itself depends on the values/types of its 
parameters. It's pure code generation. A classic example would be something like

struct Pair(T, U)
{
    T first;
    U second;
}

When you declare Pair!(int, float), the compiler creates code similar to

struct Pair!(int, float)
{
    int first;
    float second;
}

If you declared Pair!(double, double), then you'd get a second Pair struct type 
generated. Because code is being generated like this, the template parameters - 
be they types or values - must be known at compile time. So, the rules for what 
can be passed as a template argument are essentially the same as what can be 
used to initialize a static or member variable directly.


Now, with string mixins, you are also generating code. That code must obviously 
be known at compile time. And any values used in them must be known in the same 
way that they are for template parameters and the direct initialization of 
static variables and member variables. So,

mixin("int " ~ varName ~ " = " ~ varValue ~ ";");

would require that varName and varValue be known at compile time. It could be

mixin("int " ~ genVarName() ~ " = " ~ varValue!(5, 2, "hello") ~ ";");

instead (assuming that the appropriate function and template are defined), but 
the values still have to be known at compile time.


Now, as for your problem. If I understand correctly, you are looking to 
generate 
some set of classes, and have each generated class have a unique ID. You want 
to 
do this by having a counter which is incremented each time that you have create 
such a class. Well, you _can_ do it but not sanely. The problem, of course, is 
that you can't _use_ a mutable variable at compile time unless its a local 
variable in a function used with CTFE (or a member variable of a struct used in 
a function in CTFE). You can _declare_ them, but you can't use them. And for 
what you're doing, you need to use them. Unfortunately, at the moment I can 
only 
think of one possible solution, and I'm not sure that it's currently possible.

Generate a UUID for each class ID at compile time. The ID will then be unique 
without the need for each counter. You'd simply call a function with CTFE which 
generated a unique ID. Now, I'm not sure if you can make C calls with CTFE or 
not (I'm guessing not, but you might), and I doubt that anyone has written a 
UUID-generating library for D just yet. I'm also not entirely sure how UUID-
generation works, and it may require internal, mutable static variables, which 
would make this permanently impossible. So, I'm not sure that that's an option 
at the moment, or if it ever will be.


Allowing you to have mutable static variables useable at compile-time would be 
a 
serious hindrance to the language in general, complicating variable 
initializations considerably, and slow down compile times - potentially by a 
lot. So, I believe that in the general case, D made the correct decision here. 
That being said, it would be nice to have a solution for the sort of situation 
that you're currently dealing with. Unfortunately, I can't really think of one 
at the moment. Everything that I can think of would either outright not work or 
has some flaw in it which makes it so that it almost works but not quite.

Now, if you're willing to forgoe having the IDs being known at compile time, 
there _is_ a way. What you do is you initialize them in a static constructor. 
For example,

class A
{
    static this()
    {
        ID = globalIDCount++;
    }

    static immutable uint ID;
}

That way, the ID is unique, and it's still immutable. Now, this does have a 
potential problem. Normally, global and static variables are thread-local. But 
this in an immutable variable, and immutable globals or statics generally are 
treated as shared by the compiler. In fact, there is currently an open bug on 
the fact that immutable variables can be re-initialized because a static 
constructor was run in multiple threads. How this will be fixed, I don't know. 
It 
may end up requiring immutable globals and statics to be initialized in shared 
static constructors, or it may require that you have immutable static 
constructors (similar to how you can have immutable constructors) which then 
initialize all immutable globals and statics. Until then, however, you risk re-
initializing ID every time that a thread is created.

To fix this, you can make the static constructor and ID shared.

class A
{
    shared static this()
    {
        ID = globalIDCount++;
    }

    shared static immutable uint ID;
}

but that means that you then have to worry about synchronization among threads. 
You don't want two shared constructors accessing globalIDCount at the same time 
(I'm 99% sure that that can't happen, since all share, static constructors 
should run once on program startup and all run in the same thread, but I'm not 
100% certain that it's guaranteed that only one thread is used or that it's 
required by the language). But using a synchronized block on globalIDCount 
should solve that problem, I believe (I haven't used synchronized blocks much 
though, so this might not be quite correct):

class A
{
    shared static this()
    {
        synchronized(globalIDCount)
        {
            ID = globalIDCount++;
        }
    }

    shared static immutable uint ID;
}

In any case, by using static constructors and delaying the initialization of 
the 
IDs until runtime, you _should_ be able to get it to work. But you won't be 
able 
to do it at compile time like you've been trying to do.

- Jonathan M Davis

Reply via email to