This is a very old archived design page. Official docs at https://clojure.org/reference/repl_and_main
Forgive me if this is the wrong channel to ask this, but I am having trouble understanding how to version my own library written in clojure using the clojure cli and deps.edn. When looking at other projects on github I notice that nowhere does a deps.edn file contain a project version, but instead the project version lives in a separate pom file where the project's dependencies are also listed. Can someone explain why that is? Is there a way to do this without a pom.xml file? Thanks!
Clojure is a hosted language and leverages a lot of the Java and JVM ecosystem -- including Maven-style repositories for distributing libraries (e.g., http://clojars.org). That's why we're "stuck" with the pom.xml
file.
You can mostly ignore it. If you use depstar
, you can tell it to generate a minimal pom.xml
for you and keep it in sync with the dependencies in your deps.edn
file. It uses the same machinery that underlies the Clojure CLI (`-Spom`) to do that.
You can also tell depstar
what your library version is for any given JAR you build, and it will update your pom.xml
file for you.
Does leiningen just do this behind the scenes then? I haven't seen pom files show up when looking through libraries built with lein.
But it's also important to understand that Clojure libraries are nearly always packaged as source code, not compiled class files, and so you might just as well depend directly on their source code on GitHub, for example, which is something that deps.edn
supports via :git/url
and :sha
dependencies (instead of :mvn/version
).
Yes, Leiningen generates pom.xml
files (and puts them inside the JAR file -- just as depstar
does).
I haven't heard of depstar before so that will be helpful! Thanks for the explanation Sean!
See https://github.com/clojure/tools.deps.alpha/wiki/Tools for a list of CLI tools and libraries.
Also https://github.com/seancorfield/dot-clojure/ for some helpful aliases for working with CLI tooling.
As for Leiningen:
(! 525)-> lein new foobar
Generating a project called foobar based on the 'default' template.
The default template is intended for library projects, not applications.
To see other templates (app, plugin, etc), try `lein help new`.
(! 526)-> cd foobar/
(! 527)-> lein jar
Created /Users/sean/clojure/foobar/target/foobar-0.1.0-SNAPSHOT.jar
(! 528)-> jar tvf target/foobar-0.1.0-SNAPSHOT.jar |fgrep pom
1948 Sat Jan 09 16:26:36 PST 2021 META-INF/maven/foobar/foobar/pom.xml
56 Sat Jan 09 16:26:36 PST 2021 META-INF/maven/foobar/foobar/pom.properties
The pom.xml
file is used when deploying the JAR to http://clojars.org -- and also services like http://cljdoc.org lift a lot of information from it in terms of authors, version control links, etc. So the minimal file that clojure -Spom
generates doesn't have any of that but if you use clj-new
to create a new project, the pom.xml
file is fully-populated with that information.
Also services like jitpack - which is the way to use github hosted dependencies seperate from :git/url. poms are everywhere and won't die anytime soon
> "But it's also important to understand that Clojure libraries are nearly always packaged as source code, not compiled class files..." Why is this the case?
Is there a situation when I'd want one over the other when using a library?
AOT (compilation) can cause a lot of problems in libraries so we all avoid it. A library packaged as source code is likely to be compatible with a wide variety of Clojure versions. A compiled library may only be compatible with a specific version of Clojure.
Oh ok - makes sense. Thanks again ๐
I'll get back to my repl
In addition, AOT compilation tends to bring in transitive dependencies and compile them too (Leiningen goes to some lengths to try to clean up after AOT to avoid dragging in "the world" when you compile just a library so this problem is often unnoticed until further down the road).
If you're building an application for deployment to production -- as an uberjar -- then it's fine to AOT everything as the last step in building that JAR but the only impact it really has is faster start up (because you're avoiding the initial compilation of Clojure source code when your app starts up).
(although, for years at work we didn't bother AOT compiling even our uberjars because startup speed was reasonable -- but over time as our apps gained more dependencies, it was worth AOT to speed things up for rolling restarts etc... but, along with direct-linking, it makes it harder to patch a running process via a remote REPL... which is something we still do in some of our production processes)
Tools.deps and the Clojure CLI are not build tools, and don't manage versions, deployments, tests, etc. All it does is pull dependencies and launch your app with the correct dependencies passed to it for it to run.
So if you want anything else, you need to use some other tool along with Tools.deps. That's where say depstar comes in (and there are others).
Remember when they told you Clojure didn't like frameworks but favoured small composable libraries instead... Ya they were not joking ;)
is using varargs in protocol fns OK? Eg:
(defprotocol State
(update-state! [_ f & args])
(reset-state! [_ v]))
Docstrings work, I think! (The point is of course valid)
Thanks @didibusand Sam for your much appreciated advice! I like the wrapping approach and will use that. Out of curiosity, where is the official documentation for this? I scanned http://clojure.org couldnโt find any thing.
Seems that's mostly an undocumented quirk
I highly recommend clojuredocs for a reference, it has user submitted examples and notes for functions which really help with edge cases
There's a note about it there for example
There's an open ticket to change the compiler so it errors if someone tries to use & but it's probably a case of too many fish to fry and it hasn't been merged in yet
Oh I missed that one! Cool many thanks ๐
No
No, basically the &
is treated as an ordinary symbol, and it is as if you defined:
(defprotocol State
(update-state! [_ f other args])
(reset-state! [_ v]))
So the & act as a normal argument, like if you had wrote other
in its place.A trick I've seen is to wrap the protocol function in a normal function:
(defprotocol State
(-update-state! [_ f args])
(reset-state! [_ v]))
(defn update-state! [this _ f & args]
(-update-state! this _ f args))
You can also do it with a different namespace if you don't like having to change the name of the protocol:
(ns foo-protocol)
(defprotocol Foo
(foo [this args]))
(ns foo
(:require [foo-protocol :as foo]))
(extend-type String
foo/Foo
(foo [this args]
(apply str this args)))
(defn foo
[this & args]
(foo/foo this args))
(foo "hello" "world" "lol" "hi")
And I'd say that's the recommended approach for protocols. If you hadn't noticed, there's a lot that a protocol function doesn't support. You can't have have a spec, can't have a doc-string, etc. So actually providing normal functions that wraps the protocol functions is an idiom
is there a library for doing stuff with set semantics but over generic datatypes? something like
(defn difference [left right] (when (set? right) (cond (set? left) (set/difference left right) (vector? left) (filterv (complement right) left) (seq? left) (filter (complement right) left))))
https://github.com/redplanetlabs/specter can do something like that.
Has someone an idea how to walk a form (like clojure.walk does) but with passing to the function not only the value but also the path?
(defn walker
([f] (walker [] f))
([path f]
(fn [v]
(cond
(map? v)
(into {}
(map (fn [[key val]]
[key ((walker (conj path key) f) val)]))
v)
(sequential? v)
(map-indexed
(fn [idx val]
((walker (conj path idx) f) val))
v)
:else
(f path v)))))
((walker (fn [path val]
(prn path)
val))
{:x [:0 :1 :2] :y {:v 3}})
something like this?@viebel haha, maybe it needs a clearer treatmentโฆ but the idea is you keep a vector of the path as you walk, and then if you ever descend down into some indexed spot in the sequence, you carry the NEW path (ie the old path with the new index you dropped into conj
-ed on)
my impl above was a little more complicated because Iโm also carrying along the type of thing you descend into, instead if just the indices
(defn mapv-chain
"Returns a new vector of equivalent shape to `v`, generated by applying `f` to
two arguments:
- the entry in the vector
- a vector of its 'access chain', ie, the path you'd pass
to [[clojure.core/get-in]] to access the entry
For example:
(doall (mapv-chain print [[1 2] [3 4]]))
1 [0 0]
2 [0 1]
3 [1 0]
4 [1 1]"
[f v]
(letfn [(walk [elem chain]
(letfn [(process [i x]
(walk (nth elem i)
(conj chain i)))]
(if (vector? elem)
(into [] (map-indexed process elem))
(f elem chain))))]
(walk v [])))
here it is cleaned up for just vectors
I like @delaguardoโs more general version ๐
@delaguardo I like your solution
Could you explain why you chose to implement a walker instead of a walk like this?
(defn walk
([f v] (walk f [] v))
([f path v]
(cond
(map? v)
(into {}
(map (fn [[key val]]
[key (walk f (conj path key) val)]))
v)
(sequential? v)
(map-indexed
(fn [idx val]
(walk f (conj path idx) val))
v)
:else
(f path v))))
(walk (fn [path val]
(if (coll? val)
val
[path val]))
{:x [0 1]
:y {:v 3}})
Because returned walker function can be in composition of many other data processors.
ร la transducer?
Iโm more thinking about comp
)
but I see no problems to use it in transducer
I'm not 100% sure, but this reminds me of https://github.com/halgari/odin and I think it might have some similar functionality somewhere in the code
I have something for this!
https://github.com/sicmutils/sicmutils/blob/master/src/sicmutils/structure.cljc#L504
Maybe not exactly what you want but give that docstring a look