Fork me on GitHub

> > Since the release of Clojure 1.9, As I've been happily ensconced in my fortress of `project.clj` files these past six years, I've had no reason to get to grips with this tool, or any of its implications. > I've decided to apply a simple forcing function for this project: do it with `clojure` and / `deps.edn` alone — no Leiningen! > Is it possible to achieve what Lein does without it? I get that there is no specific need or benefit for it, since Lein does many things out-of-the-box, but just asking out of curiosity.. 🙂


@thegobinath At work we switched from Leiningen to Boot back in 2015 and then to the Clojure CLI in 2018. We do everything with the Clojure CLI. A 100K line monorepo, with thirty subprojects, building a dozen apps.


Awesome 👍

Ben Sless11:07:37

Do you have some resources or recommendations regarding using clj CLI for a monorepo? How do you manage it? does it require any special tooling? Why did you choose a monorepo over polyrepo?


I would be interested in this as well. Sounds like good fodder for a blog post.

Ben Sless13:07:43

my thoughts exactly 🙂


Some folks really don't like monorepos tho'.


We just use the Clojure CLI and deps.edn. The only "unusual" feature we leverage is the CLJ_CONFIG environment variable to point the "user-level" aspect to a specific subproject in the monorepo that contains our "master" deps.edn which has all the tooling aliases for the monorepo and all the "pinned" versions of libraries (where we use a library from multiple subprojects, we tend to pin it to a specific version via :override-deps under a :defaults alias in that master deps.edn file).


So our usage is the equivalent of

$ cd <repo>/subproject
$ CLJ_CONFIG=../versions clojure -A:defaults:other:aliases:here
and we have a small shell script, called build, that wraps that and also allows us to invoke one command across multiple subprojects:
$ build test worldsingles test api uberjar api
which expands to roughly this:
$ cd <repo>/worldsingles
$ CLJ_CONFIG=../versions clojure -A:defaults:test
$ cd <repo>/api
$ CLJ_CONFIG=../versions clojure -A:defaults:test
$ cd <repo>/api
$ CLJ_CONFIG=../versions clojure -A:defaults:uberjar
(the latter invokes depstar)


Dependencies between subprojects are just {:local/root ".../subproject"} in each subproject's deps.edn file.


(and there is a little bit of alias expansion involved, to compensate for the lack of composable aliases -- so test actually expands to :test:test-deps:runner, where :test is a general test-related alias in versions/deps.edn and :test-deps is empty that file but can be overridden in each subproject to provide extra testing dependencies etc and :runner is in versions/deps.edn and runs Cognitect's test runner)


is the component library used in that monorepo?


Yes, we use Component very heavily in all our projects.


Is there a good description of where to use "import, use, require, refer"?

Alex Miller (Clojure team)12:07:37

That’s good, I also point people at as well (except I think you should use vectors for import, not lists)

Nir Rubinstein14:07:05

@U064X3EF3 - That!! Exactly that! My OCD flares every time I see require with vectors followed by import with lists...

Alex Miller (Clojure team)14:07:44

and fyi, Rich had the exact same feedback when he read Stuart's post so I can second it with a voice of authority :)


I never understood Stuart's reasoning for lists: "Because the first symbol, the package name, is special." But he also provides a bit more clear reasoning: "because both ns and import have historically been documented this way." Although, ns doc also has an example of using prefix lists with :require, which I think nobody really does nowadays. Maybe if the documentation was changed, there would be less contention on the topic?


FWIW I use lists for import myself simply because I started doing so exactly after reading that article just when I started learning Clojure. Now it's just a matter of habit.


Whats the issue with list vs vec? May be i should just read the code of ns macro


They're identical in terms of the result. It's a purely stylistic issue.


I'm the Stuart Sierra camp (and my team mate seems to be as well) but I understand Rich's (and Alex's) position too.


It's just another one of those places where the early implementation didn't proscribe the syntax very strongly and now it would be hard to change without breaking a lot of code. After all, when ns was spec'd so that require and import were illegal (don't use symbols, use keywords instead) that broke quite a few libraries, including some of the Contrib stuff.


(but I'm pretty sure the symbol-based ns form was never documented -- it just happened to work when folks accidentally omitted the :?)


@U04V70XH6 Do you have any particular reasons for preferring lists over vectors for :import? I mean less ambiguous than "because the first item is special".


No, but Stuart's point on that resonates with me. And I've never seen the prefix form used in :require so they feel different to me.


That's why I also sympathize with Alex/Rich/etc that some people feel it's a rather arbitrary stylistic decision.


If I started work at a company that uses vectors with :import, I'd just follow the house style (but after a decade of using lists with :import I suspect I'd get called out in code reviews quite a bit at first!).

👍 3

What would be the common way of looping through the result of a java.lang.Process's .children and calling .destroy on that? (Java 11) something like:

(run! #(.destroy %) (iterator-seq (.iterator (.children proc))))


.children is returning a Stream


(-> (.children proc) 
      (reify Consumer 
        (accept [_ process]
          (.destroy process)))))


tbh I like the run! style better


don't have to reify anything, use the stdlib


but you should type hint % / process

👍 3

the run! style will also be supported by babashka (it has some missing classes for it now, but will be fixed) whereas reify isn't supported at all yet


meh, I am mostly resigned to a jvm-first approach to clojure


i need to try babashka sometime


rn I'm mostly using ruby and rake in places where i would be using bash otherwise


Can I not use for loops within macro definitions? A minimal example here fails with undecipherable errors


it's missing a ~

(defmacro foo []
  `(list ~@(for [i [1 2 3]] (+ 1 i))))


clojure uses ~ for syntax unquote rather than ,


Thanks a lot, I got that mixed up

👍 3

An interesting variation:

(defmacro foo []
  (println "hi")
  (for [i [1 2 3]] (println i))
  (println "bye")
This outputs
test> (foo)
I expected it to also output the 3 numbers. Does anyone know why?


This is what originally led me to think that for is silently failing inside macros. But something more nuanced seems to be going on here.


for creates a lazy sequence of values. If you are doing side effects use doseq or if you need to tie together producing the sequence with performing a side effect you can use doall on the result of a for


;; won't do side effects until "realized"
(def l (for [i [1 2 3]]
         (println i))
;; I put the def because if you try this
;; directly in the repl it will print 
;; the list and realize the lazy sequence


;; Generally what you want to do side effects
(doseq [i [1 2 3]]
  (println i))


;; If you really need it, but lazy seqs 
;; with side effects are a code smell
;; i'd say
(doall (for [i [1 2 3]] (do (println i) i)))


for is not a for loop. hit up #beginners, lots of helpful people therein


I wonder, if clj -X was designed for function that take 1 map argument, doesn't it make sense for such functions to take 0 args (as if empty map) and variadic kvs? Sort of like that:

(defn foo
  ([] (foo {}))
  ([m] m)
  ([k v & kvs]
   (foo (apply hash-map k v kvs))))
Not that I have any use for that...


I like this approach so much I want to use it everywhere 😬


It's generally more painful to work with config as varargs instead of maps


not that more painful... but more painful than writing { and } around arguments 🙂


It's for the same reason opts are passed as a map rather than varargs more and more, so you don't need to use the non-existent core function mapply everywhere

Alex Miller (Clojure team)20:07:01

we've kicked around actually turning all kwarg functions into also taking a map arg at the language level so (defn foo [& {:keys [a b]}]) could be invoked as (foo {:a 1}) or (foo :a 1 :b 2)

🙏 21

I’ve dreamed of this!


Oh, that would be a very nice enhancement!

Alex Miller (Clojure team)20:07:50

needs some research but Rich came up with that a while back and we spent like 20 minutes on it and decided it wasn't crazy :)


wouldn't that break any existing code?

Alex Miller (Clojure team)20:07:19

I don't think so - kwargs take even counts, this would be a 1-count in that spot

💯 3

ah nice 🙂

Alex Miller (Clojure team)20:07:39

and I don't think you can write a variadic that would collide

Alex Miller (Clojure team)20:07:45

but that's the research part :)


it might lead to a weird error message if you only provided a single key and forgot the val "keyword can't be cast to ilookup" :D

Alex Miller (Clojure team)20:07:16

oh sure. hand wave hand wave

🙂 3

right, that's more a joke about the error message backward construction to features we already do

Alex Miller (Clojure team)20:07:57

we'd want to figure all that out


I wonder if it would incur more runtime overhead for function calls


since you have to check things


maybe it could be part of the destructure function so you can also use this in destructuring - or maybe not 🙂

Alex Miller (Clojure team)20:07:04

I think it would have to be deeper than that


Would that be a breaking change for functions relying on the kw-arg order?


I'm thinking specifically of something like function that logs key value pairs, where the order of the arguments is important

Alex Miller (Clojure team)21:07:27

kw-arg order is not a thing


:face_palm: never mind me. Sounds like an excellent change, I've worked on a few projects which had an apply-to-map function for working with kwarg-happy libraries, would be great to ditch that


can't wait for 1.11!

Mario C.23:07:26

I have a function that is normally called like this

(attmd :_att1 (string-type) :label (custom-label :_att2 :_att3) :category [:initial]) 
its defined as (defn attmd [att-name & options]) . I am trying to write a macro that sort-of wraps that function such that I can use it like
(dynamic-attmd :_att1 (string-type) :label (custom-label :_att2 :_att3) :category [:initial])
but I can't figure out how to work with & arguments within a macro.

Mario C.23:07:45

Here is what I have

(defmacro dynamic-attmd [att-name & values-expr]
    (let [used-atts   (vec (common/expression-attributes values-expr))
          st-att-name (name att-name)]
      `[(defm/dependency ~att-name ~used-atts)
        (defm/trigger (comp (modtransform/without-attribute (keyword ~st-att-name))
                         (modtransform/with-parts [(apply attmd (keyword ~st-att-name) ~values-expr)])))]))

Mario C.23:07:09

I dont think apply is doing what I think its doing.

Jan K23:07:27

In a macro you can use unquote-splice, ie. ~@values-expr to "unwrap" the list.

🎉 3
Mario C.23:07:37

That did it! Thank you!

👍 3