December 16, 2011
On Dec 16, 2011, at 3:24 PM, Walter Bright wrote:

> On 12/16/2011 3:18 PM, maarten van damme wrote:
>> how did other languages solve this issue? I can't imagine D beeing the only language with static constructors, do they have that problem too?
> 
> In C++, the order that static constructors run is implementation defined. No guarantees at all. The programmer has no reasonable way to control the order in which they are done.
> 
> (Of course, C++ doesn't even have modules, so the notion of a module constructor is tenuous at best.)

This aspect of C++ drives me absolutely crazy.  Though I imagine it bothers a lot of people given all the coverage static initialization has gotten in C++ literature.
December 16, 2011
On Dec 16, 2011, at 3:16 PM, Andrei Alexandrescu wrote:

> On 12/16/11 5:08 PM, Sean Kelly wrote:
>> On Dec 16, 2011, at 1:38 PM, Trass3r wrote:
>> 
>>> A related issue is phobos being an intermodule dependency monster. A simple hello world pulls in almost 30 modules!
>> 
>> This was one of the major motivations for separating druntime from phobos.  The last thing anyone wants is for something in runtime to print to the console and end up pulling in 80% of the standard library as a result.
> 
> Well, right now druntime itself may have become the interdependency knot it once wanted to shun :o).

The first place to look would be rt/.  I know there's some tool that generates dependency graphs for D.  Does Descent do that?
December 16, 2011
On Friday, December 16, 2011 17:13:49 Andrei Alexandrescu wrote:
> Maybe there's an issue with the design. Maybe Singleton (the most damned of all patterns) is not the best choice here. Or maybe the use of an inheritance hierarchy with a grand total of 4 classes. Or maybe the encapsulation could be rethought.
> 
> The general point is, a design lives within a language. Any language is going to disallow a few designs or make them unsuitable for particular situation. This is, again, multiplied by the context: it's the standard library.

I don't know what's wrong with singletons. It's a great pattern in certain circumstances. In this case, it avoids unnecessary allocations every time that you do something like Clock.currTime(). There's no reason to keep allocating new instances of LocalTime and wasting memory. The data in all of them would be identical. And since the time zone has to be dynamic, it requires either a class or function pointers (or delegates). And since multiple functions are involved per time zone, it's far cleaner to use class. It has the added benefit of giving you a nice place to do stuff like ask the time zone its name. So, I don't see what could be better than using classes for the time zones like it does now. And given the fact that it's completely unnecessary and wasteful to allocate multiple instances of UTC and LocalTime, it seems to me that the singleton pattern is exactly the correct solution for this problem.

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.

> But what's the appropriate order then? :o)

It doesn't matter. The static constructors in std.datetime has no dependencies on other modules at all aside from object and the core module which holds the declaration for tzset. In neither case does it depend on any other static constructors. In my experience, that's almost always the case. But because of how circular dependencies are treated, the compiler/runtime considers it a circular dependency as soon as two modules which import each other directly - or worse, indirectly - both have module constructors, regardless of whether there is anything even vaguely interdependent about those static constructors and what they initialize. So, you're forced to move stuff into other modules, and in some cases (such as when pure or immutable is being used), that may not work.

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.

- Jonathan M Davis
December 16, 2011
On 17.12.2011 00:34, Sean Kelly wrote:
> On Dec 16, 2011, at 3:16 PM, Andrei Alexandrescu wrote:
>
>>  On 12/16/11 5:08 PM, Sean Kelly wrote:
>>>  On Dec 16, 2011, at 1:38 PM, Trass3r wrote:
>>>
>>>>  A related issue is phobos being an intermodule dependency monster.
>>>>  A simple hello world pulls in almost 30 modules!
>>>
>>>  This was one of the major motivations for separating druntime from
>>>  phobos.  The last thing anyone wants is for something in runtime to
>>>  print to the console and end up pulling in 80% of the standard
>>>  library as a result.
>>
>>  Well, right now druntime itself may have become the interdependency knot it once wanted to shun :o).
>
> The first place to look would be rt/.  I know there's some tool that generates dependency graphs for D.  Does Descent do that?

Maybe this is the tool you're thinking of:

http://www.shfls.org/w/d/dimple/
December 16, 2011
On Friday, December 16, 2011 16:58:51 Andrei Alexandrescu wrote:
> On 12/16/11 2:58 PM, Jonathan M Davis wrote:
> > Unfortunately, the necessity of tzset would remain however.
> 
> Why? From http://pubs.opengroup.org/onlinepubs/007904875/functions/tzset.html:
> 
> "The tzset() function shall use the value of the environment variable TZ
> to set time conversion information used by ctime(), localtime(),
> mktime(), and strftime(). If TZ is absent from the environment,
> implementation-defined default timezone information shall be used."
> 
> I'd expect a good standard library implementation for D would call tzset() once per process instance, lazily, inside the wrapper functions for the four functions above. Alternatively, people could call the stdc.* versions and expect tzet() to _not_ having been called.
> 
> That strikes the right balance between convenience, flexibility, and efficiency.

I mean that if CTFE was advanced enough that I could do

immutable _localTime = new LocalTime();

then I could eliminate the shared static constructor for UTC completely, but the tzset for LocalTime would still be required. It _should_ be run once per process, and it's currently in a shared static constructor, so that's what it does. It's just not currently lazy. Regardless, my point was that even if CTFE were that advanced, the static constructor would still be required. If it's changed so that it's lazily loaded, then it can be moved out of the static constructor, but the CTFE solution wouldn't be enough.

I'll look at what it would take to get rid of the static constructors and make the singletons load lazily, but it will require subverting the type system, since it's going to have to break both immutable and pure to be loaded lazily.

- Jonathan M Davis
December 17, 2011
On 12/16/2011 3:54 PM, Jonathan M Davis wrote:
> I'll look at what it would take to get rid of the static constructors and make
> the singletons load lazily, but it will require subverting the type system,
> since it's going to have to break both immutable and pure to be loaded lazily.

Sure, but having a way to tell the compiler "assume this constructor does not have any dependencies" also subverts the type system.

December 17, 2011
On 12/16/11 5:50 PM, Jonathan M Davis wrote:
> On Friday, December 16, 2011 17:13:49 Andrei Alexandrescu wrote:
>> Maybe there's an issue with the design. Maybe Singleton (the most damned
>> of all patterns) is not the best choice here. Or maybe the use of an
>> inheritance hierarchy with a grand total of 4 classes. Or maybe the
>> encapsulation could be rethought.
>>
>> The general point is, a design lives within a language. Any language is
>> going to disallow a few designs or make them unsuitable for particular
>> situation. This is, again, multiplied by the context: it's the standard
>> library.
>
> I don't know what's wrong with singletons.

http://en.wikipedia.org/wiki/Singleton_pattern

Second paragraph.

> It's a great pattern in certain
> circumstances. In this case, it avoids unnecessary allocations every time that
> you do something like Clock.currTime(). There's no reason to keep allocating
> new instances of LocalTime and wasting memory. The data in all of them would
> be identical. And since the time zone has to be dynamic, it requires either a
> class or function pointers (or delegates). And since multiple functions are
> involved per time zone, it's far cleaner to use class. It has the added
> benefit of giving you a nice place to do stuff like ask the time zone its name.
> So, I don't see what could be better than using classes for the time zones
> like it does now. And given the fact that it's completely unnecessary and
> wasteful to allocate multiple instances of UTC and LocalTime, it seems to me
> that the singleton pattern is exactly the correct solution for this problem.

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.

> 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?

>> But what's the appropriate order then? :o)
>
> It doesn't matter. The static constructors in std.datetime has no dependencies
> on other modules at all aside from object and the core module which holds the
> declaration for tzset. In neither case does it depend on any other static
> constructors. In my experience, that's almost always the case. But because of
> how circular dependencies are treated, the compiler/runtime considers it a
> circular dependency as soon as two modules which import each other directly -
> or worse, indirectly - both have module constructors, regardless of whether
> there is anything even vaguely interdependent about those static constructors
> and what they initialize. So, you're forced to move stuff into other modules,
> and in some cases (such as when pure or immutable is being used), that may not
> work.

Under what circumstances it doesn't work, 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.


Andrei
December 17, 2011
On 12/16/11 5:54 PM, Jonathan M Davis wrote:
> On Friday, December 16, 2011 16:58:51 Andrei Alexandrescu wrote:
>> On 12/16/11 2:58 PM, Jonathan M Davis wrote:
>>> Unfortunately, the necessity of tzset would remain however.
>>
>> Why? From
>> http://pubs.opengroup.org/onlinepubs/007904875/functions/tzset.html:
>>
>>
>>
"The tzset() function shall use the value of the environment variable TZ
>> to set time conversion information used by ctime(), localtime(),
>> mktime(), and strftime(). If TZ is absent from the environment,
>> implementation-defined default timezone information shall be
>> used."
>>
>> I'd expect a good standard library implementation for D would call
>> tzset() once per process instance, lazily, inside the wrapper
>> functions for the four functions above. Alternatively, people could
>> call the stdc.* versions and expect tzet() to _not_ having been
>> called.
>>
>> That strikes the right balance between convenience, flexibility,
>> and efficiency.
>
> I mean that if CTFE was advanced enough that I could do
>
> immutable _localTime = new LocalTime();
>
> then I could eliminate the shared static constructor for UTC
> completely, but the tzset for LocalTime would still be required. It
> _should_ be run once per process, and it's currently in a shared
> static constructor, so that's what it does. It's just not currently
> lazy. Regardless, my point was that even if CTFE were that advanced,
> the static constructor would still be required. If it's changed so
> that it's lazily loaded, then it can be moved out of the static
> constructor, but the CTFE solution wouldn't be enough.
>
> I'll look at what it would take to get rid of the static constructors
> and make the singletons load lazily, but it will require subverting
> the type system, since it's going to have to break both immutable and
> pure to be loaded lazily.

I think it's all a matter of terminology. Calling tzset during module
initialization is not "required", doing it otherwise is not
"impossible", and the standard library does not have to always "play it
nice". :o)

One more thing - could you take the time to explain why you believe calling tzset() compulsively is needed?


Thanks,

Andrei
December 17, 2011
On Friday, December 16, 2011 18:47:02 Andrei Alexandrescu wrote:
> One more thing - could you take the time to explain why you believe calling tzset() compulsively is needed?

Some of the C stuff that LocalTime uses requires it. If LocalTime is lazily initialized, then it can be called then though rather than in the shared static constructor.

- Jonathan M Davis
December 17, 2011
On 12/16/11 6:53 PM, Jonathan M Davis wrote:
> On Friday, December 16, 2011 18:47:02 Andrei Alexandrescu wrote:
>> One more thing - could you take the time to explain why you believe
>> calling tzset() compulsively is needed?
>
> Some of the C stuff that LocalTime uses requires it. If LocalTime is lazily
> initialized, then it can be called then though rather than in the shared
> static constructor.
>
> - Jonathan M Davis

Thanks. Sounds like we have a plan!

Andrei