Program size, linking matter, and static this()

Jonathan M Davis jmdavisProg at gmx.com
Fri Dec 16 16:54:57 PST 2011


On Friday, December 16, 2011 18:05:56 Andrei Alexandrescu wrote:
> On 12/16/11 5:50 PM, Jonathan M Davis wrote:
> http://en.wikipedia.org/wiki/Singleton_pattern
> 
> Second paragraph.

Valid points, but it's still useful under some circumstances. I don't actually 
use it very often personally. It just made sense here. Thanks for the link.

> You're using a stilted version of it. Most often the singleton object is
> created lazily upon the first access, whereas std.datetime creates the
> object (and therefore shotguns linkage with the garbage collector) even
> if never needed.
> 
> But what I'm trying here is to lift the level of discourse. The
> Singleton sounds like the solution of choice already presupposing that
> inheritance and polymorphism are good decisions. What I'm trying to say
> is that D should be rich enough to allow you considerable freedom in the
> design space, so we should have enough means to navigate around this one
> particular issue. I don't think we can say with a straight face we can't
> avoid use of static this inside std.datetime.

The only reason that it's not lazily loading is because of the purity issue an 
the fact that it would require a mutex. The mutex we can live with. pure can't 
be gotten around easily, but I'll figure it out.

As for the general design, SysTime needs to be able to dynamically adjust its 
value based on the time zone upon request (e.g. asking for the SysTime as a 
string or asking for the that SysTime's year). That essentially requires that 
the set of functions required for the calculations be swappable (preferably as 
a group, since that's far cleaner). Encapsulating it in a class gives you that 
polymorphic behavior quite nicely and also groups the various functions quite 
nicely. It also gives you a nice place to put some stuff like the time zone's 
name. Sure, we could theoretically change it to' be struct which holds 
function pointers, but that seems to me like you're pretty much just trying to 
redesign classes that way. I think that the basic design is solid.

> > There would be fewer potential issues with circular dependencies if
> > std.datetime were broken up, but the consensus seems to be that we don't
> > want to do that. Regardless, if I find a way to lazily load the
> > singletons in spite of immutable and pure, then there won't be any more
> > need for the static constructors for them. There's still one for the
> > unit tests, but worse comes to worst, that functionality could be moved
> > to a function which is called by the first unittest block.
> 
> Maybe the choice of immutable and pure is too restrictive. How about
> making the object returned const?

SysTime holds an immutable TimeZone (currently with Rebindable). In theory, 
this should have the advantage of making it possible to pass a SysTime across 
with send and receive, but bugs in the compiler currently make it impossible 
to construct and immutable SysTime. So, all TimeZone objects are const, or 
they won't work with SysTime. And since there's not normally a reason to 
change any of the values in a TimeZone (they don't hold much data in the first 
place), that's really not a problem.

The only problem with making it immutable has to do with the singleton. I 
suppose that it could be change to Rebindable!(immutable TimeZone) like in 
SysTime, but when I designed it, there didn't seem much point to that, since 
it had to be constructed at runtime and required a static constructor 
regardless. And I was trying to make absolutely as much in std.datetime pure 
as possible, which inevitably led to the singletons being pure. Making them 
impure makes it so that a variety of other functions can't be pure and would 
break code. I don't remember how much however.

Regardless, to avoid breaking code, it has to pure. It's possible that the 
code breakage would be worth it, but I'd have to mess around with it to see. 
With appropriate casts, pure can be subverted, but that's obviously ugly.

> Under what circumstances it doesn't work,

I couldn't move the singletons out of std.datetime in that way. pure disallows 
it.

> and how would adding _more_
> support for _less_ safety would be better than a glorified cast that you
> can use _today_?
>
> > Clearly, I'm not going to win any arguments on this, given that both you
> > and Walter are definitely opposed, but I definitely think that the
> > current situation with circular dependencies is one of D's major warts.
> 
> I'm not nailed to the floor. Any good arguments would definitely change
> my opinion.

I don't think that I have ever seen an _actual_ circular dependency when a 
program blows up because of it. It's always a case of the two modules doing 
completely unrelated stuff with their static constructors. It's generally 
incredibly obvious that there's no interdependency, but the compiler/runtime 
isn't smart enough to see that. And if you use static constructors much (which 
invariably happens if you have much in the way of immutable variables which 
are commonly used enough to put at module or class scope), you run into this 
problem fairly easily. And given the large amount of inter-module importing in 
Phobos, it's _very_ easy to run into the problem there if we use static 
constructors.

When such circular dependencies happen, it's a royal pain to sort out what's 
going on - especially if the modules to import each other directly. The error 
messages have improved, but it's still nasty to sort out exactly what's 
happening. And then fixing it? Assuming that you can use the solution that some 
of Phobos' modules use by having a secondary module for the initialization, 
then there's a way to do it, but that solution is quite ugly IMHO, and 
regardless of that, it's _not_ in the least bit obvious. I don't know that I 
ever would have thought of it myself (maybe, maybe not).

So, the programmer is essentially faced with a situation where they have two 
modules with static constructors that they can clearly see are completely 
unrelated, but they're going to have to do some major refactoring to get 
around the issue that the compiler and runtime _aren't_ smart enough to see 
that there order that the modules are initialized doesn't matter at all. _If_ 
they think of the solution that Phobos uses or are lucky enough to have 
someone else points it out to them _and_ it's actually possible to refactor 
the static constructor out like that, then the solution is doable, albeit 
arguably on the ugly side. But that's assuming a lot IMHO.

By contrast, we could have a simple feature that was explained in the 
documenation along with static constructors which made it easy to tell the 
compiler that the order doesn't matter - either by saying that it doesn't 
matter at all or that it doesn't matter in regards to a specific module. e.g.

@nodepends(std.file)
static this()
{
}

Now the code doesn't have to be redesigned to get around the fact that the 
compiler just isn't smart enough to figure it out on its own. Sure, the feature 
is potentially unsafe, but so are plenty of other features in D. The best 
situation would be if the compiler was smart enough to figure it out for 
itself, but barring that this definitely seems like a far cleaner solution than 
having to try and figure out how to break up some of the initialization code 
for a module into a separate module, especially when features such as 
immutable and pure tend to make such separation impossible without some nasty 
casts. It would just be way simpler to have a feature which allowed you to 
tell the compiler that there was no dependency.

I'd probably feel differently about this if static constructors tended to have 
actual interdependencies, but they are almost invariably used for initializing 
immutable variables and the like and have no dependencies on other modules at 
all. It's other stuff in the modules which have those interdependencies.

- Jonathan M Davis


More information about the Digitalmars-d mailing list