View mode: basic / threaded / horizontal-split · Log in · Help
March 22, 2011
Re: Strategies for resolving cyclic dependencies in static ctors
On 03/22/2011 04:14 PM, Michel Fortin wrote:
> On 2011-03-22 05:16:31 -0400, Max Samukha <max@spam.box> said:
>
>> ----
>> module foo_helper;
>>
>> private extern(C) foo_static_ctor();
>> static this()
>> {
>> foo_static_ctor();
>> }
>>
>> -----
>>
>> module foo;
>> import foo_helper;
>>
>> private Object global;
>> private extern(C) void foo_static_ctor()
>> {
>> global = new Object;
>> }
>> ----
>>
>> Note that "global" is guaranteed to have been initialized when
>> accessed from static constructors in modules that import "a".
>
> I don't know why people keep repeating that falacy.

It's not people, only me. It's embarrassing. I deserve to be processed 
in a bioreactor on a charge of utter incompetency.

Of course, you are absolutely right.

Then why we keep losing last bits of sanity inventing workarounds? The 
problem obviously has no safe solution in the context of D. People have 
been asking for a solution for a long time. There is an obvious need for 
it. So why not just give us that damn pragma?
March 22, 2011
Re: Strategies for resolving cyclic dependencies in static ctors
On Mon, 21 Mar 2011 20:12:55 -0400, Nick Sabalausky <a@a.a> wrote:

> I'm intending this thread as somewhat of a roundtable-like discussion.
> Hopefully we can come up with good material for a short article on  
> Wiki4D,
> or maybe the D website, or wherever.
>
> The scenario: A coder is writing some D, compiles, runs and gets a  
> "Cyclic
> dependency in static ctors" error. Crap! A pain for experienced D users,  
> and
> very bad PR for new D users. (Hopefully we'll eventually get some sort of
> solution for this, but in the meantime D users just have to deal with  
> it.)
>
> The question: What now? What strategies do people find useful for dealing
> with this? Any specific "first steps" to take? Best practices? Etc.

What one can try is to factor out the initialization code into a separate  
module.  Essentially if you have:

module a;
import f : someFunction;
import b; // conflicts because of circular dependencies

int aglobal;

static this()
{
  aglobal = someFunction();
}

You can do something like:

module a_static;
import f : someFunction;

int aglobal;

static this()
{
   aglobal = someFunction();
}

in a.d:
module a;
public import a_static;
import b;

Of course, there can be reprecussions -- you may need to have aglobal  
declared in a.d.  In those cases, one can try to hide the cycle as Max  
Samuckha stated, but I'd rather see a compiler option than these kinds of  
workarounds.  The workarounds can be unobvious, but can be just as  
dangerous.

-Steve
March 22, 2011
Re: Strategies for resolving cyclic dependencies in static ctors
On 23/03/11 03:41, Steven Schveighoffer wrote:
> On Mon, 21 Mar 2011 20:12:55 -0400, Nick Sabalausky <a@a.a> wrote:
>
>> I'm intending this thread as somewhat of a roundtable-like discussion.
>> Hopefully we can come up with good material for a short article on 
>> Wiki4D,
>> or maybe the D website, or wherever.
>>
>> The scenario: A coder is writing some D, compiles, runs and gets a 
>> "Cyclic
>> dependency in static ctors" error. Crap! A pain for experienced D 
>> users, and
>> very bad PR for new D users. (Hopefully we'll eventually get some 
>> sort of
>> solution for this, but in the meantime D users just have to deal with 
>> it.)
>>
>> The question: What now? What strategies do people find useful for 
>> dealing
>> with this? Any specific "first steps" to take? Best practices? Etc.
>
> What one can try is to factor out the initialization code into a 
> separate module.  Essentially if you have:
>
> module a;
> import f : someFunction;
> import b; // conflicts because of circular dependencies
>
> int aglobal;
>
> static this()
> {
>   aglobal = someFunction();
> }
>
> You can do something like:
>
> module a_static;
> import f : someFunction;
>
> int aglobal;
>
> static this()
> {
>    aglobal = someFunction();
> }
>
> in a.d:
> module a;
> public import a_static;
> import b;
>
> Of course, there can be reprecussions -- you may need to have aglobal 
> declared in a.d.  In those cases, one can try to hide the cycle as Max 
> Samuckha stated, but I'd rather see a compiler option than these kinds 
> of workarounds.  The workarounds can be unobvious, but can be just as 
> dangerous.
>
> -Steve

My own solution to this "problem" is to never have circular imports at 
all. The build system I use prohibits them, so any careless introduction 
of a circularity is spotted immediately and I refactor the code to 
eliminate the circularity. I have never come across a valid need for 
circularities, and have never had any trouble eliminating any that creep in.

Avoiding circularities has plenty of advantages, like progressive 
development, testing and integration. On bigger projects these 
advantages are very important, and even on small ones they are useful.

-- 
Graham St Jack
March 23, 2011
Re: Strategies for resolving cyclic dependencies in static ctors
"Graham St Jack" <Graham.StJack@internode.on.net> wrote in message 
news:imbai9$2jb9$1@digitalmars.com...
>
> My own solution to this "problem" is to never have circular imports at 
> all. The build system I use prohibits them, so any careless introduction 
> of a circularity is spotted immediately and I refactor the code to 
> eliminate the circularity. I have never come across a valid need for 
> circularities, and have never had any trouble eliminating any that creep 
> in.
>
> Avoiding circularities has plenty of advantages, like progressive 
> development, testing and integration. On bigger projects these advantages 
> are very important, and even on small ones they are useful.
>

That's certainly good in many cases, but I find there are many times when a 
"one-way" dependency graph just doesn't fit the given problem and causes 
more trouble than it solves. You often end up needing to re-invent the wheel 
to avoid a dependency, or split/arrange/merge modules in confusing 
unintuitive ways that have more to do with implementation detail than 
high-level purpose.
March 23, 2011
Re: Strategies for resolving cyclic dependencies in static ctors
On 23/03/11 15:12, Nick Sabalausky wrote:
> "Graham St Jack"<Graham.StJack@internode.on.net>  wrote in message
> news:imbai9$2jb9$1@digitalmars.com...
>> My own solution to this "problem" is to never have circular imports at
>> all. The build system I use prohibits them, so any careless introduction
>> of a circularity is spotted immediately and I refactor the code to
>> eliminate the circularity. I have never come across a valid need for
>> circularities, and have never had any trouble eliminating any that creep
>> in.
>>
>> Avoiding circularities has plenty of advantages, like progressive
>> development, testing and integration. On bigger projects these advantages
>> are very important, and even on small ones they are useful.
>>
> That's certainly good in many cases, but I find there are many times when a
> "one-way" dependency graph just doesn't fit the given problem and causes
> more trouble than it solves. You often end up needing to re-invent the wheel
> to avoid a dependency, or split/arrange/merge modules in confusing
> unintuitive ways that have more to do with implementation detail than
> high-level purpose.
>
>
>
I'm happy to admit that these cases could come up, but I have never yet 
seen one where the design wasn't improved by removing the circularity.


-- 
Graham St Jack
March 23, 2011
Re: Strategies for resolving cyclic dependencies in static ctors
Graham St Jack wrote:
> On 23/03/11 15:12, Nick Sabalausky wrote:
>> "Graham St Jack"<Graham.StJack@internode.on.net>  wrote in message
>> news:imbai9$2jb9$1@digitalmars.com...
>>> My own solution to this "problem" is to never have circular imports at
>>> all. The build system I use prohibits them, so any careless introduction
>>> of a circularity is spotted immediately and I refactor the code to
>>> eliminate the circularity. I have never come across a valid need for
>>> circularities, and have never had any trouble eliminating any that creep
>>> in.
>>>
>>> Avoiding circularities has plenty of advantages, like progressive
>>> development, testing and integration. On bigger projects these 
>>> advantages
>>> are very important, and even on small ones they are useful.
>>>
>> That's certainly good in many cases, but I find there are many times 
>> when a
>> "one-way" dependency graph just doesn't fit the given problem and causes
>> more trouble than it solves. You often end up needing to re-invent the 
>> wheel
>> to avoid a dependency, or split/arrange/merge modules in confusing
>> unintuitive ways that have more to do with implementation detail than
>> high-level purpose.
>>
>>
>>
> I'm happy to admit that these cases could come up, but I have never yet 
> seen one where the design wasn't improved by removing the circularity.
> 
> 
I wish Phobos didn't have any circular dependencies. Unfortunately, 
there are almost no modules which aren't in a loop (Basically, anything 
which imports std.range is a lost cause). There is no doubt that it 
hurts debugging.
March 23, 2011
Re: Strategies for resolving cyclic dependencies in static ctors
On 03/23/2011 12:12 AM, Graham St Jack wrote:
> Avoiding circularities has plenty of advantages, like progressive development,
> testing and integration.

Maybe it depends on your app domain or whatnot; there are lots of cases, I 
guess, where circularities are inevitable, if not direct expression of the problem.
Take for instance a set of factories (eg parsing pattern) defined in a M1 
producing reesults (eg parse tree nodes) defined in M2. It's clear that M1 
imports M2. Then, how do you unittest M2? You should import M1... Sure, there 
are various workarounds (creating fake pattern types or objects, exporting the 
tests in a 3rd module...), but they are only this: workarounds.

Denis
-- 
_________________
vita es estrany
spir.wikidot.com
March 23, 2011
Re: Strategies for resolving cyclic dependencies in static ctors
"spir" <denis.spir@gmail.com> wrote in message 
news:mailman.2690.1300879902.4748.digitalmars-d@puremagic.com...
> On 03/23/2011 12:12 AM, Graham St Jack wrote:
>> Avoiding circularities has plenty of advantages, like progressive 
>> development,
>> testing and integration.
>
> Maybe it depends on your app domain or whatnot; there are lots of cases, I 
> guess, where circularities are inevitable, if not direct expression of the 
> problem.
> Take for instance a set of factories (eg parsing pattern) defined in a M1 
> producing reesults (eg parse tree nodes) defined in M2. It's clear that M1 
> imports M2. Then, how do you unittest M2? You should import M1... Sure, 
> there are various workarounds (creating fake pattern types or objects, 
> exporting the tests in a 3rd module...), but they are only this: 
> workarounds.
>

Funny, I had a couple parsing examples in mind, too: If you have a 
general-purpose (ie, grammar-agnostic) parsing tool, then it may make sense 
for the parse tree nodes (ine one module) to know what Language (from 
another module) they're part of. If it's a grammar-agnostic parsing tool, 
this information can't be encoded in the type. Or, if you have a variety of 
parsing-error-related types (exceptions, for instance), then if they need to 
know what Language they're from, you can't put them in a separate module 
without creating a cycle.

Another thing is string-processing vs general-purpose string-mixin 
utilities: If you have a bunch of CTFE-compatible string-processing 
functions, and a bunch of general-purpose string-mixin-based utilities, it 
makes sense to have them in separate modules. The general-purpose 
string-mixin utilities are almost certainly going to depend on the 
string-processing functions. But if the string-mixin utilities are indeed 
general-purpose, it's likely that some of them may be very useful to the 
string-processing module.

So avoiding cycles can involve some real contortions in certain cases. But I 
do agree they're certainly good to avoid whenever it's reasonable and 
practical to do so.
March 24, 2011
Re: Strategies for resolving cyclic dependencies in static ctors
Regarding unit tests - I have never been a fan of putting unit test code 
into the modules being tested because:
* Doing so introduces stacks of unnecessary imports, and bloats the module.
* Executing the unittests happens during execution rather than during 
the build.

All unittests (as in the keyword) seem to have going for them is to be 
an aid to documentation.

What I do instead is put unit tests into separate modules, and use a 
custom build system that compiles, links AND executes the unit test 
modules (when out of date of course). The build fails if a test does not 
pass.

The separation of the test from the code under test has plenty of 
advantages and no down-side that I can see - assuming you use a build 
system that understands the idea. Some of the advantages are:
* No code-bloat or unnecessary imports.
* Much easier to manage inter-module dependencies.
* The tests can be fairly elaborate, and can serve as well-documented 
examples of how to use the code under test.
* Since they only execute during the build, and even then only when out 
of date, they can afford to be more complete tests (ie use plenty of cpu 
time)
* If the code builds, you know all the unit tests pass. No need for a 
special unittest build and manual running of assorted programs to see if 
the tests pass.
* No need for special builds with -unittest turned on.


-- 
Graham St Jack
March 24, 2011
Re: Strategies for resolving cyclic dependencies in static ctors
> Regarding unit tests - I have never been a fan of putting unit test code
> into the modules being tested because:
> * Doing so introduces stacks of unnecessary imports, and bloats the module.
> * Executing the unittests happens during execution rather than during
> the build.
> 
> All unittests (as in the keyword) seem to have going for them is to be
> an aid to documentation.
> 
> What I do instead is put unit tests into separate modules, and use a
> custom build system that compiles, links AND executes the unit test
> modules (when out of date of course). The build fails if a test does not
> pass.
> 
> The separation of the test from the code under test has plenty of
> advantages and no down-side that I can see - assuming you use a build
> system that understands the idea. Some of the advantages are:
> * No code-bloat or unnecessary imports.
> * Much easier to manage inter-module dependencies.
> * The tests can be fairly elaborate, and can serve as well-documented
> examples of how to use the code under test.
> * Since they only execute during the build, and even then only when out
> of date, they can afford to be more complete tests (ie use plenty of cpu
> time)
> * If the code builds, you know all the unit tests pass. No need for a
> special unittest build and manual running of assorted programs to see if
> the tests pass.
> * No need for special builds with -unittest turned on.

Obviously, it wouldn't resolve all of your concerns, but I would point out 
that you can use version(unittest) to enclose stuff that's only supposed to be 
in the unit tests build. And that includes using version(unittest) on imports, 
avoiding having to import stuff which is only needed for unit tests during 
normal builds.

- Jonathan M Davis
1 2 3
Top | Discussion index | About this forum | D home