Utility libraries and dependency hygiene

875 views
Skip to first unread message

Stuart Sierra

unread,
May 12, 2013, 7:29:28 PM5/12/13
to clo...@googlegroups.com
Based on a recent thread about "utility" libraries, I would like to
take this opportunity to ask everyone to help us avoid the dependency
mess that Common Lisp has gotten into, where there are over a dozen
such "convenience" libraries[1].

By all means, use these libraries in your *applications* if you find
them useful. But please don't make your *libraries* depend on them
unless you really need to.

Doing this will help application developers who want to use your
library. For example, if my application depends on libraries A, B, and
C, I might end up with transitive dependencies on three different
"utility" libraries. If I want to add library D which depends on an
incompatible version of one of those utilities, I'm stuck. By
adding to the dependencies of your library, you increase the
likelihood of dependency conflicts for consumers.

The ideal number of dependencies for a library release (not counting
Clojure itself) is zero. Obviously, use common sense. If your library
relies on critical functionality in another library, then make the
dependency. But if you can get by without the dependency (even if that
means copying some of those "utility" functions into your own code and
making them private) then you will make life easier for consumers of
your library.

Thanks, and happy coding.
-S


[1]: http://cliki.net/Convenience%20library

Angel Java Lopez

unread,
May 12, 2013, 7:45:05 PM5/12/13
to clo...@googlegroups.com
Only a lateral quick comment:

Node.js + NPM (its package manager) has a nice version management.

If your module A needs D version 0.1, and module B needs D version 0.2, no problem. Both versions are added, and A loads version 0.1, meanwhile B loads version 0.2. The trick is the search path for modules: each module has its own subfolder for dependencies

I guess it could be difficult to implement such feature in Java/Clojure

Angel "Java" Lopez
@ajlopez



--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
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 clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Stuart Sierra

unread,
May 12, 2013, 9:20:19 PM5/12/13
to clo...@googlegroups.com
Isolated dependency loading is not possible in the JVM without complex ClassLoader-based schemes like OSGI, which come with their own set of problems.

-S

Mikera

unread,
May 12, 2013, 10:55:52 PM5/12/13
to clo...@googlegroups.com
On Monday, 13 May 2013 09:20:19 UTC+8, Stuart Sierra wrote:
Isolated dependency loading is not possible in the JVM without complex ClassLoader-based schemes like OSGI, which come with their own set of problems.

-S


Hi Stuart,

That's true for compiled Java classes on the classpath, but I don't see why it needs to be true for JVM languages like Clojure that are already loading / compiling most code at runtime. In effect we already have a "complex ClassLoader based scheme".

Maybe we could try to develop towards some kind of lightweight dependency loading system that avoids this problem?

A lot of utility functions are so small that it wouldn't matter if you loaded them many times. It might even improve performance if the JIT is able to optimise different instantiations of functions based on how they are being used by different parts of the code base.

The alternatives aren't pretty:
1. We do a lot of "cut and paste" coding. OK in small doses, but not going to scale.
2. We get into dependency deadlocks with different libraries using different versions
3. Everyone develops their own utility libraries independently and manages against this
4. Everyone co-ordinates their dependency updates (yeah, like that's going to happen...)
5. We wait 10 years until Java develops a decent module system that we can use

I'm personally feeling this problem a lot: the move towards small composable libraries in Clojure is IMHO the right direction, but I'm concerned that it's making the dependency management problem more complex and we haven't (yet) got the tools to handle this. The nightmare scenario is that we end up with thousands of individually cool and useful libraries, but no practical way to make them work together.

I touched on a few of these themes with my post "The Environment as a Value", which would be one way to solve a lot of this via isolation of different versions of code:


Dave Kincaid

unread,
May 12, 2013, 11:53:47 PM5/12/13
to clo...@googlegroups.com
Thanks for this, Stuart. I hope it's not too late. As one who has spent the last couple weeks spinning up a new project that uses its own local Ivy repository I've been feeling this pain first hand. The number of dependencies I have had to add just for a few Clojure libraries has been quite painful. I attributed it to how young the language is that these utility libraries haven't had time to consolidate.

Alex Baranosky

unread,
May 13, 2013, 3:25:31 AM5/13/13
to clo...@googlegroups.com
Yes, by all means please just copy-n-paste out of https://github.com/runa-dev/kits if it simplifies your dependency tree.

--

Stuart Sierra

unread,
May 13, 2013, 4:35:14 AM5/13/13
to clo...@googlegroups.com

On Mon, May 13, 2013 at 12:55 PM, Mikera <mike.r.an...@gmail.com> wrote:
Maybe we could try to develop towards some kind of lightweight dependency loading system that avoids this problem?


I believe "lightweight dependency loading system" is an oxymoron. Either you A) design a new module format and try to get everyone to follow it (OSGI) or B) build an ad-hoc solution that tries to guess at the right behavior (JBoss). Either way, application developers still have a mess to deal with, just with an added layer of complexity.

If someone out there can fix this problem once and for all, I will buy you a drink. But you can't bill me for all the drinks you'll need along the way. ;)

-S

Meikel Brandmeyer (kotarak)

unread,
May 13, 2013, 4:42:32 AM5/13/13
to clo...@googlegroups.com, ma...@stuartsierra.com
Hi,


Am Montag, 13. Mai 2013 10:35:14 UTC+2 schrieb Stuart Sierra:


I believe "lightweight dependency loading system" is an oxymoron. Either you A) design a new module format and try to get everyone to follow it (OSGI) or B) build an ad-hoc solution that tries to guess at the right behavior (JBoss). Either way, application developers still have a mess to deal with, just with an added layer of complexity.

If someone out there can fix this problem once and for all, I will buy you a drink. But you can't bill me for all the drinks you'll need along the way. ;)


Write  a tool which rebases the dependency in your namespace tree. Et voila. No nifty class loader magic necessary. As long as you always use *ns* to get hold of the namespace name, this should work.

Meikel

Glen Mailer

unread,
May 13, 2013, 4:56:22 AM5/13/13
to clo...@googlegroups.com, ma...@stuartsierra.com
Are dependencies the potential problem, or are "utility belt" dependencies the issue here?

If its the latter, then should we be trying to break down the utility belt into groups of related functionality, which can be stable and focused.

Promoting zero-dependency libraries just means re-inventing a bunch of wheels, I believe node.js has this right by making dependencies cheap and easy and promoting publishing reusable libraries with only a few functions in. Not being able to load multiple versions of the same dependency make this slightly harder, but the general goal appears to me to be a good one.

Glen

Dave Ray

unread,
May 13, 2013, 12:57:37 PM5/13/13
to clo...@googlegroups.com
In Java-land this tool is called Jar Jar Links
(http://code.google.com/p/jarjar/).

Phil Hagelberg

unread,
May 13, 2013, 1:25:52 PM5/13/13
to clo...@googlegroups.com

Angel Java Lopez writes:

> I guess it could be difficult to implement such feature in Java/Clojure

It's really not difficult to do if you limit yourself to Clojure since
Clojure namespaces are first-class and easy to manipulate at
run-time. We implemneted a prototype of this in under two hours at a
Seajure meeting a while back:

https://github.com/technomancy/metaverse

However, it's significantly more difficult to do for arbitrary Java bytecode.

-Phil

zcaudate

unread,
May 13, 2013, 3:39:40 PM5/13/13
to clo...@googlegroups.com
I'm guilty of this... But I really don't know what to do.... I still find myself thinking... Hmmm this library provides about 50% of what I want... This library has 2 functions that I like.... And I don't like the way something is implemented here... If only i can combine this, this, and this... Oh screw it! I'm going to write my own ;)

Any suggestions?

Stuart Sierra

unread,
May 13, 2013, 4:27:44 PM5/13/13
to clo...@googlegroups.com

On Tue, May 14, 2013 at 3:25 AM, Phil Hagelberg <ph...@hagelb.org> wrote:
It's really not difficult to do if you limit yourself to Clojure since
Clojure namespaces are first-class and easy to manipulate at
run-time. We implemneted a prototype of this in under two hours at a
Seajure meeting a while back:

    https://github.com/technomancy/metaverse

However, it's significantly more difficult to do for arbitrary Java bytecode.


That's cool, and it will work for the simple case of libraries A and B depending on different versions of C.

But it still breaks down in more complex cases: e.g. if I want to share data between A and B using a protocol or type defined in C, and there are 2 incompatible versions of C. Even ClassLoaders can't help you there - I'm not aware of any solution.

-S

Phil Hagelberg

unread,
May 13, 2013, 4:54:26 PM5/13/13
to clo...@googlegroups.com

Stuart Sierra writes:
> That's cool, and it will work for the simple case of libraries A and B
> depending on different versions of C.
>
> But it still breaks down in more complex cases: e.g. if I want to share
> data between A and B using a protocol or type defined in C, and there are 2
> incompatible versions of C. Even ClassLoaders can't help you there - I'm
> not aware of any solution.

One of many great reasons not to use protocols and records. =)

-Phil

fmj...@gmail.com

unread,
May 13, 2013, 6:08:01 PM5/13/13
to clo...@googlegroups.com
Or a reason to integrate via data, not api or type.

Timothy Baldridge

unread,
May 13, 2013, 8:08:19 PM5/13/13
to clo...@googlegroups.com

Neither of these snarky answers solve the problem. I just spent an entire week updating modules from version of a library that expected a seq of maps to one that expected map of seqs of maps.  The very nature of data implies a format. If that format changes you have a compatibility issue no fancy namespace system can solve.

Timothy

--

Phillip Lord

unread,
May 14, 2013, 5:22:59 AM5/14/13
to clo...@googlegroups.com
Automatically, no, but the solution would be to use something akin to an
adaptor. The two versions of C would be manipulated to be in different
namespaces; now you just have two libraries, so the task of plumbing
them together remains the same.

To be honest, though, this is unlikely; after all, if you are using A
and B, and they are using C *as a utility*, my feeling is that C
shouldn't really be in their public interface. If C *is* in their public
interface, then again, you need adaptors.

Or you can fork A and/or B, fix them to use the same version!

Phil

Dave Kincaid

unread,
May 14, 2013, 8:19:15 AM5/14/13
to clo...@googlegroups.com
This thread seems to have gotten way off track and I think that Stuart made a very important point in the original post that's getting lost. It would help all of us out if library authors stopped making their libraries dependent on 10+ other libraries. This issue does have the potential to really drag on Clojure adoption. Just try to maintain your own local repository for your projects and you'll see what I mean the first time you need to add a Clojure dependency.

Dave Sann

unread,
May 14, 2013, 10:51:15 AM5/14/13
to clo...@googlegroups.com
It may be pragmatic currently but manually copying code is a bad solution. Not depending on libraries is not a good solution either. The premise that there are only 3 levels of composition - clojure -> library -> application - I think is wrong, even if it works and is useful in some cases.

Don't depend on things you don't need to. But don't turn this approach into dogma and give up on better answers.

I think there are 3 problems here:
  1. Possible library conflict - this is primarily a Naming conflict - i.e. currently you can only have one version because they are named the same.
  2. Data incompatibility - this is incompatibility when passing data across interface boundaries.
  3. Number of dependency versions - but only if you need to care about size.
Library versioning is currently another example of place oriented programming. i.e same global name, different content => problem.

Dave

Glen Mailer

unread,
May 15, 2013, 4:17:59 AM5/15/13
to clo...@googlegroups.com
At the risk of making a slight strawman here, I agree this issue has the potential to drag on Clojure adoption - but in the opposite way to what you describe.

At the root of it, is the Clojure ecosystem a "dependencies are bad" system, or a "dependencies are good" system?

The former encourages roll-your-own, but reduces code sharing and re-use
The latter promotes sharing and building upon the work of others, but requires more advanced dependency management tooling

As great as the language is, having the general advice be "don't have dependencies in your libraries" seems like a step backwards to me.

I would always rather spend an hour finding a documented, tested existing library, than spend 20 minutes creating my own duplicate of that work.

I hope this doesn't come over as too accusatory, if the cost of dependencies truly is higher than the cost of duplicating work/code then perhaps we need to try and make the former easier.

Glen

Dave Kincaid

unread,
May 15, 2013, 8:19:29 AM5/15/13
to clo...@googlegroups.com
As long as we remember that not everyone is using Leiningen and/or Maven. There are other build and dependency systems out there, so any change to dependency management tooling will have to cover all of them. 

There are already popular libraries that don't work outside of Leiningen, but that's a topic for another thread...

Laurent PETIT

unread,
May 15, 2013, 9:13:19 AM5/15/13
to clo...@googlegroups.com
2013/5/15 Dave Kincaid <kincai...@gmail.com>:
> As long as we remember that not everyone is using Leiningen and/or Maven.
> There are other build and dependency systems out there, so any change to
> dependency management tooling will have to cover all of them.
>
> There are already popular libraries that don't work outside of Leiningen,
> but that's a topic for another thread...

Hello,

What are the popular Clojure (not ClojureScript) libraries that don't
work outside a maven-style-backed-central-public-repository?

Daniel

unread,
May 15, 2013, 9:28:14 AM5/15/13
to clo...@googlegroups.com
That seems orthogonal to the problem the loader is trying to solve.

Dave Kincaid

unread,
May 15, 2013, 9:28:19 AM5/15/13
to clo...@googlegroups.com
One that I encountered last week was Midje. In trying to work through all of its dependencies I discovered that Pomegranate is also dependent on Maven, but I think I was able to work around that by adding 4 or 5 Maven libraries into our repository as dependencies (not ideal, but it looked like it would work). But I still wasn't able to get Midje working due some assumptions about Leiningen.

Michael Fogus

unread,
May 15, 2013, 9:35:17 AM5/15/13
to Clojure
> take this opportunity to ask everyone to help us avoid the dependency
> mess that Common Lisp has gotten into, where there are over a dozen
> such "convenience" libraries[1].

Are Common Lispers actively suffering under this problem? With the
emergence of QuickLisp, CL dependency problems seem to have been
smoothed over.

> adding to the dependencies of your library, you increase the
> likelihood of dependency conflicts for consumers.

Agreed. But is the solution to strive for zero-dependencies? That
seems extreme. How should we view contrib libraries? Should we avoid
depending on them too? From my perspective, I try to minimize
dependencies, but if I need a library then I use it.

The Clojure community is very flexible, yet pragmatic. Maybe a better
solution to zero-dependency is a "suite" of common libraries driven by
the community, that is not affiliated with Core and contrib? I don't
know, just a thought. It would lay somewhere between the more strict
contrib and the wild-west model of NPM and JS micro-libs.

fmj...@gmail.com

unread,
May 15, 2013, 10:14:21 AM5/15/13
to clo...@googlegroups.com
I think this discussion would greatly benefit from some hammock development time ;)
The original post was quite precise in its scope, but we quickly side-stepped into related issues, so it feels like we have lost some focus. On the other hand I think many of us get the feeling we can't provide some satisfactory answer by just narrowing our discussion to the original proposed scope. We sense there's a bigger picture here and that's why we easily get carried away. So since the first step in hammock development is to state the problem, understand it, provide facts, context, constraints, trade-offs, etc. I propose we just do that.

Let's start by naming the related areas that are at play here:
  1. The design paradigm:
    How to design libraries with the right level of granularity and dependencies.
    How to make dependency layers (e.g. core libraries, then common utility libraries, etc.).
    This is the level where this discussion started, but it quickly went into the other complementary aspects below.

  2. The programming model:
    How we logically org
    anize the code into elements such as packages and namespaces.
    How we physically bundle the code into deployable units such as jars, wars, bundles, etc.
    The tools and api for defining and composing units, defining and resolving dependencies, such as leiningen, maven, ejb, spring, etc.
  3. The runtime model:
    How the deployable units are discovered and loaded.
    Whether they can be loaded at boot time or dynamically at runtime (e.g. hot-deploy).
    Whether 
    dependencies can also be resolved at runtime.
    Whether separate versions can coexists at runtime.

That's just one way to decompose the problem into related areas, inspired by [1], and there are certainly other ways to decompose the problem. So the above is just a start that may provide more context to this discussion. Of particular interest is that fact that since Clojure is a hosted language, each host language has its own story to tell about 1,2, and 3. In the case of Java we're talking jars, classloaders, osgi, jigsaw in jdk9, etc. In JavaScript we're talking about namespaces, script loading, google closure dependency management, etc. I don't know about CLR but they certainly have their own story. So the key point here is that, while building the Clojure story on modularity, we need to be aware of how it maps to each host language. And if we were to better see the trade-offs we make, it could be a good exercise to come up with the ideal story for Clojure regardless of host languages, and then compare. My guess is that we'll discover we don't need much on the Clojure side of things, but that the real challenge is in how to integrate with the OO-twisted way of doing things in each host language.

Which brings me to this: I think there's a window of opportunity between now and jigsaw in jdk9, because we won't be able to ignore their story. Rather than wait for it to be complete, it may be wise to be proactive and see what they're up to. I say this because by providing some input into the relevant JSRs we could avoid a stronger impedance mismatch between the Java/OO way of doing things, and the FP/Clojure way. For example:  will the way of generating/loading classes at runtime be made easier? Will jigsaw make it easier to load separate versions of Clojure? At boot time or at runtime? Are interfaces the best and only way to define services, or would some form of duck typing be supported? Out of all the JVM languages Clojure is probably the one that has the most interesting things to say on FP best practices. Other languages are either hybrids, and therefore lack motivation for purity, or don't have the weight of the Clojure community.

Finally, I can't help but think about Rich's presentation on the Language of Systems [2] and how this discussion fits into it. It seems to me there's some overlapping somewhere...

Anyway, how about a wiki page we could use to keep some sort of blackboard on how to better state the problem, provide facts, context, constraints, trade-offs, etc.?
Then we can each have our own separate and private hammock time to hammer this down.


Phil Hagelberg

unread,
May 15, 2013, 12:12:28 PM5/15/13
to clo...@googlegroups.com

Timothy Baldridge writes:

> Neither of these snarky answers solve the problem. I just spent an entire
> week updating modules from version of a library that expected a seq of maps
> to one that expected map of seqs of maps. The very nature of data implies
> a format. If that format changes you have a compatibility issue no fancy
> namespace system can solve.

That is quite orthogonal to do with the topic at hand. Bridging two
different libraries is often going to take some data munging. The fact
that the two different libraries happen to have the same artifact-id and
group-id is incidental once you start treating code as values. In fact,
a better namespacing system would actually make it much easier to deal
with because it would make it possible for both libraries to exist at
the same time, much in the same way persistent data structures allow old
versions of a vector to remain stable in the presence of change.

-Phil

Stuart Sierra

unread,
May 15, 2013, 6:17:17 PM5/15/13
to clo...@googlegroups.com

On Wed, May 15, 2013 at 11:35 PM, Michael Fogus <mef...@gmail.com> wrote:
Are Common Lispers actively suffering under this problem?

I certainly suffered from it back when I used Common Lisp. Every library was written in its own dialect of CL based on a different set of these "utilities." It made it hard to read code written by anyone else.

-S

Stuart Sierra

unread,
May 15, 2013, 6:23:23 PM5/15/13
to clo...@googlegroups.com
I did not intend to say that libraries should never have any dependencies at all. (Perhaps "zero" was too strong in my original post.)

I only ask that libraries try to avoid any *unnecessary* dependencies. Many "utility" libraries fall into this category, especially if you only depend on them for 1 or 2 functions.

Given the dependency management tools we have right now, this will make life easier for users of your library.

Thanks,
-S



On Mon, May 13, 2013 at 9:29 AM, Stuart Sierra <the.stua...@gmail.com> wrote:
Based on a recent thread about "utility" libraries, I would like to
take this opportunity to ask everyone to help us avoid the dependency
mess that Common Lisp has gotten into, where there are over a dozen
such "convenience" libraries[1].

By all means, use these libraries in your *applications* if you find
them useful. But please don't make your *libraries* depend on them
unless you really need to.

Doing this will help application developers who want to use your
library. For example, if my application depends on libraries A, B, and
C, I might end up with transitive dependencies on three different
"utility" libraries. If I want to add library D which depends on an
incompatible version of one of those utilities, I'm stuck. By
adding to the dependencies of your library, you increase the
likelihood of dependency conflicts for consumers.

The ideal number of dependencies for a library release (not counting
Clojure itself) is zero. Obviously, use common sense. If your library
relies on critical functionality in another library, then make the
dependency. But if you can get by without the dependency (even if that
means copying some of those "utility" functions into your own code and
making them private) then you will make life easier for consumers of
your library.

Thanks, and happy coding.
-S


[1]: http://cliki.net/Convenience%20library

--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/WuS31RSiz_A/unsubscribe?hl=en.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

Brian Marick

unread,
May 15, 2013, 12:35:52 PM5/15/13
to clo...@googlegroups.com

On May 15, 2013, at 8:28 AM, Dave Kincaid <kincai...@gmail.com> wrote:

> One that I encountered last week was Midje. In trying to work through all of its dependencies I discovered that Pomegranate is also dependent on Maven, but I think I was able to work around that by adding 4 or 5 Maven libraries into our repository as dependencies (not ideal, but it looked like it would work). But I still wasn't able to get Midje working due some assumptions about Leiningen.

This will be fixed in 1.6. I'm taking the "minimize dependencies" advice to heart for those libraries that Midje doesn't use much of.

--------
Latest book: /Functional Programming for the Object-Oriented Programmer/
https://leanpub.com/fp-oo

Laurent PETIT

unread,
May 15, 2013, 6:51:36 PM5/15/13
to clo...@googlegroups.com
Hello,

2013/5/16 Stuart Sierra <ma...@stuartsierra.com>:
How is the advice of each library re-creating for itself little
utility functions, again and again, going to address the specific
concern of "made it hard to read code written by anyone else" ?

>
> -S
>
> --
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@googlegroups.com
> Note that posts from new members are moderated - please be patient with your
> first post.
> To unsubscribe from this group, send email to
> clojure+u...@googlegroups.com
> 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

Stuart Sierra

unread,
May 15, 2013, 7:58:10 PM5/15/13
to clo...@googlegroups.com

On Thu, May 16, 2013 at 8:51 AM, Laurent PETIT <lauren...@gmail.com> wrote:
How is the advice of each library re-creating for itself little
utility functions, again and again, going to address the specific
concern of "made it hard to read code written by anyone else" ?


If the functions are in the same file or project they're easier to find when you're browsing the code.

-S

Devin Walters

unread,
May 15, 2013, 9:00:41 PM5/15/13
to clo...@googlegroups.com
I must admit that I haven't read every response in this thread but my gut feeling is that a lot of the "I just need a couple of functions" situations would be mitigated by making non-core devs feel more welcome to suggesting and contributing modular contrib ideas. I hate to bring up process because I think there is more discussion to be had that is valuable and completely unrelated. I know why we have new contrib, but old contrib seemed to inspire smaller contributions.

Sent via Mobile


--

fmj...@gmail.com

unread,
May 16, 2013, 8:18:53 AM5/16/13
to clo...@googlegroups.com
Using the decomposition I made earlier, I would refine my analysis a bit further and say:
the original problem lies mostly within the programming model: it's about resolving dependencies when reusing and composing deployable units, and how to avoid complexity and conflicts.
- part of the problem also lies within the runtime model: as of yet Clojure core is not expressive enough to support the use of two versions of the same library at runtime.
- the proposed solution belongs to the design paradigm: some form of heuristics on the number of dependencies, copying code, and the sense of having some sort of layer of utility libraries.
So in fact the problem and proposed solution were already spread across all these domains to start with.
Let's continue with more facts while summarizing what's already been discussed:
- there is a difference between managing and resolving dependencies during development, and doing the same for preparing deployable units; lein uberjar and google closure dependency management and compilation/minification are great examples.
- talking about leingingen and automating code copying/renaming is searching for a solution within the programming model.
- talking about classloaders and using different versions at runtime is searching for a solution within the runtime model.
- the homoiconic and dynamic nature of Clojure means that programming and runtime models are intimately related: classes are generated and loaded at runtime, and dependencies can be resolved at runtime (e.g. pomegranate).
- not everyone and every host language use leiningen, so the solution cannot be about leiningen only, but the latter can certainly facilitate the process of applying a solution.

If we were to organize possible solutions at each level:
  1. design paradigm: what the original post proposed, i.e. heuristic on dependencies, manually copying code, and the sense of having some sort of layer of utility libraries.
  2. programming model: automate the copying and/or re-namespacing of code during the creation of deployable units, e.g. some form of :copy/:copy-as directives within the ns form; and/or we could bring version specification into the ns form, although ns has no knowledge of deployment units, so some form of mapping needs to be done and/or we need some cross-platform reification of deployment units (e.g. some generalization of jars, wars, ears, osgi bundles, modules in jigsaw, minified scripts, etc.).
  3. runtime model: depends largely on the host language: classloaders in java, jigsaw later, closure minification in clojurescript, but that's a programming model solution.
I'm sure there's more. And a combination of all these solutions certainly makes sense.
We can start at level 1 with the original proposed solution, but the pressure of moving into solutions at level 2 is already felt in this discussion.
Moving into level 3 solutions later on should be the natural continuation, and that should be present in our minds while working on other levels.

Mark Engelberg

unread,
Jun 5, 2013, 6:55:53 PM6/5/13
to clo...@googlegroups.com
This thread came up right around the time I was considering adding a dependency on rhizome to instaparse to make it easy to visualize the parse trees.  Based on the discussion here, I decided it would be a bad idea to include rhizome directly in instaparse's dependencies.  Nevertheless, it made sense to enable the "visualize" function *provided* rhizome was already in the user's dependencies.  More specifically, it will use whichever version of rhizome they choose to put in their dependencies, thus a version conflict is not an issue.

I found a technique to achieve this effect, and wanted to report on it here.

First, towards the top of the file, after the namespace declaration, I included the following:

(try
  (require '[rhizome.viz :as r])
  (catch Exception e
    (require '[instaparse.viz-not-found :as r])))
This sets things up so r/ refers either to the rhizome.viz namespace, or if it is not installed, my dummy instaparse.viz-not-found namespace.  The instaparse.viz-not-found contains stubs for all the functions from rhizome that I use; the stubs simply throw a friendly error message saying to add rhizome to the project's dependencies.


Stuart Sierra

unread,
Jun 6, 2013, 9:36:35 AM6/6/13
to clo...@googlegroups.com
puzzler wrote:
> I decided it would be a bad idea to include rhizome
> directly in instaparse's dependencies. Nevertheless, it
> made sense to enable the "visualize" function *provided*
> rhizome was already in the user's dependencies.

I prefer to avoid these kinds of load-time tricks, as they
break easily when anything else is involved in loading code,
e.g. an IDE or app server.

Fortunately, Rhizome is driven by data. If you can provide
functions that return the data structures that Rhizome
expects, then Instaparse users can easily invoke Rhizome
themselves to get the visualizations.

If this is not possible, I would suggest providing the
visualization tools as a separate library release which
depends on both Instaparse and Rhizome. This might make
sense anyway, if they are intended only as a
development-time tool.

-S
Reply all
Reply to author
Forward
0 new messages