I've been programming Clojure for something like 6 years now, and yes, I've
hit the cyclic dependency error a few times. How did I solve it? Each time
via abstraction, and parameterization of functions. Most of the time this
means writing a "interfaces.clj" file that contains all my defprotocols and
defmulti statements, then I go and write namespaces that use these abstract
interfaces. So instead of B requiring A directly it requires InterfaceA
from the interfaces.clj file.

Now for global data, the simple answer is to don't use global data. Instead
of NamespaceA requiring def'd data from NamespaceB, just have all the
functions in NamespaceA require a hash map of dependency data. Now
everything is pure, and you can write NamespaceC that wires everything up.

So the layout looks like this:

Interfaces.clj
             |
------------------------------------------------
|                              |
ImplementationA   Implementation B
|                              |
------------------------------------------------
         |
Orchestration (Setup) Namespace


I haven't encountered a problem yet that can't be solved with these
methods. Maybe they will help you as well.

Timothy

On Fri, Dec 30, 2016 at 4:08 PM, Aaron Cohen <[email protected]> wrote:

> You already know this has been discussed a lot over the years.
>
> I just wanted to cite the best reasoning that I've seen from Rich about
> why Clojure does it the way that it does, which I believe is the argument
> that surrounded an blog post fro Steve Yegge that Rich responded to and
> some excellent discussion surrounding it on hackernews and the mailing
> list: https://news.ycombinator.com/item?id=2466731
>
> On hackernews, Rich made a post that I believe is the best "last word"
> explaining his reasoning, which I'll quote here:
>
> The issue is not single-pass vs multi-pass. It is instead, what
>> constitutes a compilation unit, i.e., a pass over what?
>>
>> Clojure, like many Lisps before it, does not have a strong notion of a
>> compilation unit. Lisps were designed to receive a set of
>> interactions/forms via a REPL, not to compile files/modules/programs etc.
>> This means you can build up a Lisp program interactively in very small
>> pieces, switching between namespaces as you go, etc. It is a very valuable
>> part of the Lisp programming experience. It implies that you can stream
>> fragments of Lisp programs as small as a single form over sockets, and have
>> them be compiled and evaluated as they arrive. It implies that you can
>> define a macro and immediately have the compiler incorporate it in the
>> compilation of the next form, or evaluate some small section of an
>> otherwise broken file. Etc, etc. That "joke from the 1980's" still has
>> legs, and can enable things large-unit/multi-unit compilers cannot. FWIW,
>> Clojure's compiler is two-pass, but the units are tiny (top-level forms).
>>
>> What Yegge is really asking for is multi-unit (and larger unit)
>> compilation for circular reference, whereby one unit can refer to another,
>> and vice versa, and the compilation of both units will leave hanging some
>> references that can only be resolved after consideration of the other, and
>> tying things together in a subsequent 'pass'. What would constitute such a
>> unit in Clojure? Should Clojure start requiring files and defining
>> semantics for them? (it does not now)
>>
>> Forward reference need not require multi-pass nor compilation units.
>> Common Lisp allows references to undeclared and undefined things, and
>> generates runtime errors should they not be defined by then. Clojure could
>> have taken the same approach. The tradeoffs with that are as follows:
>>
>> 1) less help at compilation time 2) interning clashes
>>
>> While #1 is arguably the fundamental dynamic language tradeoff, there is
>> no doubt that this checking is convenient and useful. Clojure supports
>> 'declare' so you are not forced to define your functions in any particular
>> order.
>>
>> #2 is the devil in the details. Clojure, like Common Lisp, is designed to
>> be compiled, and does not in general look things up by name at runtime.
>> (You can of course design fast languages that look things up, as do good
>> Smalltalk implementations, but remember these languages focus on dealing
>> with dictionary-carrying objects, Lisps do not). So, both Clojure and CL
>> reify names into things whose addresses can be bound in the compiled code
>> (symbols for CL, vars for Clojure). These reified things are 'interned',
>> such that any reference to the same name refers to the same object, and
>> thus compilation can proceed referring to things whose values are not yet
>> defined.
>>
>> But, what should happen here, when the compiler has never before seen bar?
>>
>>     (defn foo [] (bar))
>>
>> or in CL:
>>
>>     (defun foo () (bar))
>>
>> CL happily compiles it, and if bar is never defined, a runtime error will
>> occur. Ok, but, what reified thing (symbol) did it use for bar during
>> compilation? The symbol it interned when the form was read. So, what
>> happens when you get the runtime error and realize that bar is defined in
>> another package you forgot to import. You try to import other-package and,
>> BAM!, another error - conflict, other-package:bar conflicts with
>> read-in-package:bar. Then you go learn about uninterning.
>>
>> In Clojure, the form doesn't compile, you get a message, and no var is
>> interned for bar. You require other-namespace and continue.
>>
>> I vastly prefer this experience, and so made these tradeoffs. Many other
>> benefits came about from using a non-interning reader, and interning only
>> on definition/declaration. I'm not inclined to give them up, nor the
>> benefits mentioned earlier, in order to support circular reference.
>>
>> Rich
>>
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to [email protected]
> Note that posts from new members are moderated - please be patient with
> your first post.
> To unsubscribe from this group, send email to
> [email protected]
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en
> ---
> You received this message because you are subscribed to the Google Groups
> "Clojure" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to [email protected].
> For more options, visit https://groups.google.com/d/optout.
>



-- 
“One of the main causes of the fall of the Roman Empire was that–lacking
zero–they had no way to indicate successful termination of their C
programs.”
(Robert Firth)

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to