clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
alexmiller 2021-01-10T00:11:18.029900Z

This is a very old archived design page. Official docs at https://clojure.org/reference/repl_and_main

clayton 2021-01-10T00:15:21.032200Z

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!

seancorfield 2021-01-10T00:21:44.032300Z

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.

seancorfield 2021-01-10T00:22:51.032500Z

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.

seancorfield 2021-01-10T00:23:23.032700Z

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.

clayton 2021-01-10T00:24:29.032900Z

Does leiningen just do this behind the scenes then? I haven't seen pom files show up when looking through libraries built with lein.

seancorfield 2021-01-10T00:24:53.033100Z

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

๐Ÿ‘ 1
seancorfield 2021-01-10T00:25:18.033300Z

Yes, Leiningen generates pom.xml files (and puts them inside the JAR file -- just as depstar does).

clayton 2021-01-10T00:25:38.033500Z

I haven't heard of depstar before so that will be helpful! Thanks for the explanation Sean!

seancorfield 2021-01-10T00:27:51.033800Z

See https://github.com/clojure/tools.deps.alpha/wiki/Tools for a list of CLI tools and libraries.

seancorfield 2021-01-10T00:28:20.034Z

Also https://github.com/seancorfield/dot-clojure/ for some helpful aliases for working with CLI tooling.

seancorfield 2021-01-10T00:29:26.034200Z

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

seancorfield 2021-01-10T00:31:19.034400Z

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.

๐Ÿ‘ 1
emccue 2021-01-10T00:32:33.034600Z

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

clayton 2021-01-10T00:33:15.035Z

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

clayton 2021-01-10T00:33:37.035200Z

Is there a situation when I'd want one over the other when using a library?

seancorfield 2021-01-10T00:38:03.035400Z

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.

clayton 2021-01-10T00:38:51.035600Z

Oh ok - makes sense. Thanks again ๐Ÿ™‚

clayton 2021-01-10T00:39:09.035800Z

I'll get back to my repl

seancorfield 2021-01-10T00:39:14.036Z

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

seancorfield 2021-01-10T00:40:36.036200Z

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

๐Ÿ‘ 1
seancorfield 2021-01-10T00:42:52.036500Z

(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)

2021-01-10T03:31:21.036700Z

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.

2021-01-10T03:32:06.037Z

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

2021-01-10T03:33:22.037200Z

Remember when they told you Clojure didn't like frameworks but favoured small composable libraries instead... Ya they were not joking ;)

2021-01-10T03:46:30.038Z

is using varargs in protocol fns OK? Eg:

(defprotocol State 
  (update-state! [_ f & args])
  (reset-state! [_ v]))

Sam Ritchie 2021-01-10T21:20:21.043100Z

Docstrings work, I think! (The point is of course valid)

2021-01-11T12:55:58.062Z

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.

2021-01-11T16:46:28.076200Z

Seems that's mostly an undocumented quirk

2021-01-11T16:47:19.076900Z

I highly recommend clojuredocs for a reference, it has user submitted examples and notes for functions which really help with edge cases

2021-01-11T16:47:38.077500Z

There's a note about it there for example

2021-01-11T16:48:43.077900Z

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

2021-01-11T19:35:43.103400Z

Oh I missed that one! Cool many thanks ๐Ÿ™‚

2021-01-10T04:29:38.038200Z

No

2021-01-10T05:42:53.038300Z

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.

2021-01-10T06:03:25.038800Z

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))

2021-01-10T06:22:05.039Z

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

2021-01-10T06:26:34.039200Z

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

nivekuil 2021-01-10T09:26:51.039700Z

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))))

p-himik 2021-01-10T10:55:58.039800Z

https://github.com/redplanetlabs/specter can do something like that.

๐Ÿ‘ 1
Yehonathan Sharvit 2021-01-10T17:15:07.040700Z

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?

2021-01-11T09:07:53.050300Z

(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?

Sam Ritchie 2021-01-11T13:34:36.062600Z

@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)

Sam Ritchie 2021-01-11T13:35:00.062800Z

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

Sam Ritchie 2021-01-11T13:40:10.063Z

(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 [])))

Sam Ritchie 2021-01-11T13:40:14.063200Z

here it is cleaned up for just vectors

Sam Ritchie 2021-01-11T13:40:39.063400Z

I like @delaguardoโ€™s more general version ๐Ÿ™‚

Yehonathan Sharvit 2021-01-11T13:55:27.063600Z

@delaguardo I like your solution

Yehonathan Sharvit 2021-01-11T13:56:31.063900Z

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}})

2021-01-11T13:59:02.064100Z

Because returned walker function can be in composition of many other data processors.

Yehonathan Sharvit 2021-01-11T14:26:23.064800Z

ร  la transducer?

2021-01-11T15:15:34.065600Z

Iโ€™m more thinking about comp ) but I see no problems to use it in transducer

2021-01-10T17:19:07.040800Z

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

Sam Ritchie 2021-01-10T21:06:15.041500Z

I have something for this!

Sam Ritchie 2021-01-10T21:07:55.042500Z

Maybe not exactly what you want but give that docstring a look