March 22, 2011
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
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
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
"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
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
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
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
"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
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
> 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