Fork me on GitHub
#clojure
<
2020-07-31
>
kosengan07:07:52

> https://www.stuttaford.me/2018/02/18/a-clojure-learning-journey/#no-leiningen > Since the release of Clojure 1.9, https://clojure.org/guides/deps_and_cli. 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 https://clojure.org/reference/deps_and_cli / `deps.edn` alone — no Leiningen! > https://www.stuttaford.me/2018/02/18/a-clojure-learning-journey/ 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.. 🙂

seancorfield07:07:44

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

kosengan08:07:32

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?

mafcocinco12:07:15

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

Ben Sless13:07:43

my thoughts exactly 🙂

seancorfield16:07:50

Some folks really don't like monorepos tho'.

seancorfield16:07:08

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

seancorfield16:07:33

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)

seancorfield16:07:15

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

seancorfield16:07:40

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

Nassin17:07:46

is the component library used in that monorepo?

seancorfield17:07:58

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

Ody10:07:27

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 https://stuartsierra.com/2016/08/27/how-to-ns 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 :)

p-himik14:07:43

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?

p-himik14:07:00

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.

Ody16:07:53

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

p-himik16:07:48

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

seancorfield16:07:26

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.

seancorfield16:07:11

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.

seancorfield16:07:57

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

p-himik16:07:35

@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".

seancorfield17:07:19

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.

seancorfield17:07:02

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

seancorfield17:07:50

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
borkdude16:07:37

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

borkdude16:07:36

.children is returning a Stream

emccue16:07:24

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

ghadi16:07:25

tbh I like the run! style better

ghadi16:07:33

don't have to reify anything, use the stdlib

ghadi16:07:55

but you should type hint % / process

👍 3
borkdude16:07:15

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

emccue17:07:14

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

emccue17:07:30

i need to try babashka sometime

emccue17:07:15

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

peey19:07:41

Can I not use for loops within macro definitions? A minimal example here https://stackoverflow.com/q/63198344/1412255 fails with undecipherable errors

phronmophobic20:07:55

it's missing a ~

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

phronmophobic20:07:58

clojure uses ~ for syntax unquote rather than ,

peey20:07:58

Thanks a lot, I got that mixed up

👍 3
peey20:07:08

An interesting variation:

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

peey20:07:19

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.

emccue23:07:07

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

emccue23:07:46

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

emccue23:07:04

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

emccue23:07:35

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

ghadi20:07:23

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

vlaaad20:07:31

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

vlaaad18:08:40

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

borkdude20:07:28

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

vlaaad20:07:19

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

borkdude20:07:48

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
telekid23:08:17

I’ve dreamed of this!

seancorfield20:07:36

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

borkdude20:07:00

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
borkdude20:07:38

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

noisesmith20:07:03

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
noisesmith20:07:38

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

borkdude20:07:07

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

borkdude20:07:21

since you have to check things

borkdude20:07:19

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

nwjsmith21:07:49

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

nwjsmith21:07:52

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

nwjsmith21:07:49

: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

vlaaad21:07:09

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