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

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

"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.

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

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.

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

"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.

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

> 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