Fork me on GitHub
#clojure
<
2022-10-09
>
grzm04:10:58

Any thoughts on how to manage "releases" of libraries that are git deps only? For example, how to announce a new release, or how to structure Release notes? Release notes are pretty much just the git history in this case. I suppose I could tag particular commits anyway and use that. What are others doing in this case?

seancorfield04:10:56

So I treat it just like a regular library, except its coordinates are different.

grzm04:10:39

Makes sense. Thanks, @U04V70XH6

didibus07:10:00

Only annoying thing is, you have to update the readme after for the git sha. So at first I just do :git/sha "..." and then you have to push a new commit with the sha updated.

grav06:10:10

Can I somehow set the default namespace to something else than user when booting up a repl with clj?

dpsutton06:10:43

❯ clj -M -e "(doto 'clojure.set require in-ns)" -r
clojure.set
clojure.set=>

chef_kiss 2
grav06:10:47

Thanks @U11BV7MTK! There's actually a good point on https://practical.li/clojure-staging/alternative-tools/clojure-tools/set-namespace-on-repl-startup.html: > It is not necessary to set the namespace when evaluating code in a Clojure aware editor. Expressions are evaluated within the scope of the namespace in which they are defined.

👍 1
grav06:10:11

I should try and stick to the editor 🙂

seancorfield07:10:42

I pretty much never type into the REPL -- my editor takes care of ensuring the right ns is used for evaluation.

seancorfield07:10:43

(I mostly have the REPL view in my editor hidden or minimized and use tap> to send interesting values to Portal for visual inspection)

👍 1
Dallas Surewood08:10:10

On windows, (clojure.java.shell/sh ...)crashes no matter what code I run. Is there a reason for this?

; Execution error (IOException) at java.lang.ProcessImpl/create (ProcessImpl.java:-2).
; CreateProcess error=2, The system cannot find the file specified

Martin Půda09:10:43

Try this: (clojure.java.shell/sh "cmd" "/C" ...) . Some commands even work without that- for example (sh "notepad.exe")

Dallas Surewood09:10:13

That worked, thank you. I'll have to set up a function that checks the environment to append "cmd" "/C"

borkdude13:10:25

@U042LKM3WCW What was the concrete invocation, i.e. the arguments on the dots? It's probably best to diagnose the root cause than to work around it

borkdude13:10:34

I might have an idea, but this is why I'm asking ;)

Dallas Surewood17:10:29

Anything crashed on windows without Martins suggestion

Dallas Surewood17:10:08

(sh "ls) or (sh "dir") crashed

chucklehead17:10:05

On windows those are built in commands of cmd. Things like notepad work because they’re executables in some directory on the PATH

borkdude17:10:22

right, yes, ls is a shell built-in. this isn't related to Windows vs linux

Dallas Surewood18:10:53

And on Linux is ls a built in of bash or is it just ls?

borkdude18:10:31

Some things are both a shell built-in and a binary:

$ bb -e '(str (fs/which "ls"))'
"/bin/ls"

Dallas Surewood18:10:05

Alright. I was using kit-framework and it was using linux/unix specific commands for its build files

Dallas Surewood18:10:22

Like sh cp and things like that

borkdude18:10:58

Ah I see. If you use a library like https://github.com/babashka/fs there is no need to shell out for copying

Dallas Surewood19:10:42

I know babashka normally as a sort of "clojure to shell script" compiler. Is there a way I can use it for platform agnostic copying in my builds?

borkdude19:10:36

babashka/fs is a Clojure and babashka library

borkdude19:10:01

btw babashka isn't a clojure to shell script compiler either, but that doesn't matter for the sake of this discussion ;)

Dallas Surewood19:10:25

Looking through babashka/fs, the API has a copy, but I'll also need to run an npx script. Is there anyway to do that or should I just use (windows? One of the commands I'm running is (sh "cmd" "/C" "npx" "shadow-cljs" "release" "app"). Maybe there's a shadow-cljs clojure call I can make instead

borkdude19:10:57

@U042LKM3WCW There is another library for this, called babashka.process: https://github.com/babashka/process You can use (process/shell "npx shadow-cljs release app") and this should work cross-platform, whether the binary is called npx.cmd or npx.exe or npx

waffletower17:10:31

Is there a work-around for specifying referential defaults for keyword arguments to a function when there are 9 or more key/value pairs? For example:

(defn panda
  [& {:keys [a b c d e f g h]
      :or {a 1
           b a
           c b
           d c
           e d
           f e
           g f
           h g}}]
  [a b c d e f g h])
panda happily compiles. You can create default values that refer to previously defined symbolics. Presumably, as the backing for the :or hashmap is an instance of clojure.lang.PersistentArrayMap. However, simply add one additional keyword argument:
(defn sad-panda
  [& {:keys [a b c d e f g h i]
      :or {a 1
           b a
           c b
           d c
           e d
           f e
           g f
           h g
           i h}}]
  [a b c d e f g h i])
and the function fails to compile, giving the syntax error: Unable to resolve symbol: h in this context

seancorfield17:10:58

Hash maps have no order so you're just "lucky" that the smaller example works -- because it is implemented using an array map under the hood and the keys stay in order.

waffletower17:10:02

I believe this is failing due to the optimizing behavior of hashmap class selection (one of my few peeves with Clojure's core design)

seancorfield17:10:29

Once you have a bigger hash map, the order is random, so you cannot have one key's default be another key's value.

waffletower17:10:03

I understand why, I wonder if there is a simple work around to force instantiation of clojure.lang.PersistentArrayMap

seancorfield17:10:27

What you're trying to do isn't intended to work and I think it's a bug that it does for small cases.

waffletower17:10:58

Oh, self-referential defaults are only a side-effect and not intended behavior?

waffletower17:10:19

That is really weird to think of that as a bug

seancorfield17:10:24

Correct. I believe I've seen Alex confirm this in another thread when the question cropped up here before.

seancorfield17:10:12

The destructuring code could detect this and throw an exception -- but that would also slow it down for everyone. It's one of those "garbage in, garbage out" cases where the behavior is deliberately undefined (but just happens to produce the result you want sometimes).

waffletower17:10:13

I think that is just a justification for the hashmap behavior of clojure.lang.PersistentHashMap

seancorfield17:10:04

That would impact the performance of a lot of code -- and it would still "sometimes work" which would be even worse.

waffletower17:10:12

I think clojure would benefit from greater core control over the instantiation classes. There is a way to have your cake and eat it too here

seancorfield17:10:18

(since it would then depend on the key names and hashes, not the ordering)

waffletower17:10:03

There are many contexts where array-map is preferred behavior, and you need to perform gymnastics around clojure.lang.PersistentHashMap

phronmophobic17:10:10

> That is really weird to think of that as a bug Maps are unordered. Even though the keys are ordered in the source code, the clojure compiler doesn't eval text, it evals data structures. So even before eval, the keys are unordered when the source code is read.

waffletower17:10:12

They are only unordered in clojure.lang.PersistentHashMap

seancorfield17:10:43

I've been doing Clojure in production for about twelve years at this point and, in my experience, this is a non-issue. Destructured hash map defaults cannot depend on each other is the intention of the design.

waffletower17:10:44

They are ordered in clojure.lang.PersistentArrayMap

waffletower17:10:11

We all know you are very experienced Sean 😄

phronmophobic17:10:36

out of curiosity, what happens if you add a reader tag in front of the map?

seancorfield17:10:15

I think it would be great if clj-kondo or Eastwood could detect and flag this as unsafe code.

pppaul17:10:57

i think using a let binding is much better than a huge :or though that is a bit context sensitive (i have 100+ line destructuring where using inline :ors seemed to help clean things up). i think :or with dynamic/variable binding is weird, though it's possible that these symbols are via config. i've never seen :or with self referencing ever. that's really for let bindings

waffletower17:10:58

I am writing this on the weekend, and definitely not going to run a linter on it, since that is something I do with code during the week

seancorfield17:10:09

My editor runs linting on all code I write 🙂

2
☝️ 1
p-himik17:10:13

@U7RJTCH6J IIRC, the read map will be a hash one, so your ordered/map reader can't really figure out the order unless it reads the source file.

👍 1
🧠 1
waffletower17:10:20

that is a reasonable conclusion to come to. I was hoping to avoid the extra let. I think it is more succinct and clean to support this in the :or binding

pppaul17:10:58

for the most part i agree with the :or being a good place to do this, if you can get rid of the self referencing stuff then it's probably ok. you could probably do that via a macro

p-himik17:10:35

You can do it with a macro indeed, but it will expand into a let, without any map literals.

pppaul17:10:07

i think that's ok, as the macro would be for improving documentation

p-himik17:10:47

> I think it is more succinct and clean to support this in the :or binding Succinct - yes. Clean - not sure what exactly it means. An argument against it is that supporting ordered self-referential destructuring requires either new syntax or changing the {} reader. In either case, it's pretty much a no-go given how niche and rare such a requirement is.

waffletower17:10:25

You can substitute clear/explanatory for clean

pppaul17:10:34

clean is subjective

seancorfield17:10:50

There's a big "gotcha" with :or that a lot of people trip up on: if you have {:keys [a b c] :or {a 1} :as opts} and you pass {:b 2 :c 3} but use opts in the function, it won't have :a 1 because the defaults only apply to the named keys, not to the full hash map. Where you want the whole hash map to have those defaults, you have to use let/`merge` or something similar in the function body.

🤯 2
waffletower17:10:53

Localizing the default logic to the :or form makes sense

waffletower17:10:13

I assumed that :keys was evaluated first, and provided symbols in order, as it is a vector

phronmophobic17:10:14

you wouldn't need to change the {} reader. In theory, it seems like the following could be supported (I'm not saying it should be supported).

(defn sad-panda
  [& {:keys [a b c d e f g h i]
      :or
      #ordered/map
      [[a 1]
       [b a]
       [c b]
       [d c]
       [e d]
       [f e]
       [g f]
       [h g]
       [i h]]}]
  [a b c d e f g h i])
But it doesn't currently work (the reader tag does return an ordered map, but the destructuring order doesn't match).

waffletower17:10:48

The backing for :or wouldn't need to matter

pppaul17:10:58

@U7RJTCH6J i feel that a let binding is probably a lot better at conveying the idea than that solution, but that is pretty cool that it can be done.

phronmophobic17:10:24

Yea, I'm not saying it's a good idea. I've stopped using :or and prefer to use let in all cases.

seancorfield17:10:34

"I assumed that :keys was evaluated first" -- since the whole thing is a hash map, there's no order for :keys, :or, :as either... [edited to remove the hand-waving about semantics and ordering because it's not really relevant]

p-himik17:10:48

> Localizing the default logic to the :or form makes sense It will complicate the logic of the map reader and incur some extra cognitive load. Right now, it's as simple as it can possibly be - a map reader produces a map. That's it, it never promises any order or any specific map type. And the fact that under 9 elements it happens to be an array map is nothing but an implementation detail. No other literal has any context-dependent meaning. Lists are lists, vectors are vectors, and so on. If maps are treated in a special way, apart from just complicating things, it will also establish a dangerous precedent.

seancorfield17:10:54

Yeah, I'm with @U7RJTCH6J -- I hardly ever use :or except in very limited cases where I know I'm only going to be dealing the specified keys, not the whole map, and the defaults are all simple literal values.

waffletower17:10:22

I don't use :or often in production.. in my hobby code I have a lot of parameters typically. Since this is the first time I have crossed the threshold, I have only now realized that that symbol reference in :or forms isn't actually supported.

waffletower17:10:35

I clearly missed the memo that it was undefined behavior. I can clearly see how it could be supported with the current declaration structure of defn. But I am glad I see the truth now

phronmophobic17:10:58

Don't try this at home! Anyway, this seems to work:

(defn sad-panda
  [& #ordered/map
   {:keys [a b c d e f g h i]
    :or
    #ordered/map
    [[a 1]
     [b a]
     [c b]
     [d c]
     [e d]
     [f e]
     [g f]
     [h g]
     [i h]]}]
  [a b c d e f g h i])
I tested thousands of permutations of the keys
(def binding '[a b c d e f g h i])
(defn ordered-binding [keys]
  (ordered-map
   (cons [(first keys) 1]
         (map (fn [prev next]
                [next prev])
              keys
              (rest keys))))
  )

(defn binding->fn-code [keys]
  (let [or-binding (ordered-binding keys)
        m (ordered-map
           :keys keys
           :or or-binding)
        fn-code `(fn [~m]
                  ~keys)]
    fn-code))

(doseq [keys (repeatedly 5000 #(shuffle binding))]
  (let [f (eval (binding->fn-code (vec keys)))]
    (assert (= [1 1 1 1 1 1 1 1 1]
               (f {(-> keys first keyword) 1})) )))

waffletower17:10:49

I would try that at home, but not at work 😄

Alex Miller (Clojure team)18:10:46

The values of :or default maps should not be expressions that depend on the locals you are binding, anything else is undefined and it’s behavior may change in the future

Alex Miller (Clojure team)18:10:49

Any appearance that this works now is implementation details leaking, not intentional behavior

waffletower18:10:50

Thanks for confirming Alex!

seancorfield18:10:15

I deliberately did not @ you on a Sunday -- but thanks for chiming in, Alex! 🙂

waffletower18:10:30

(defn panda
  [& {:keys [a b]
      :or {a 1
           b (inc a)}}]
  [a b])

(defn sad-panda
  [& {:keys [a b]
      :or {a 1}}]
  (let [b (or b (inc a))]
    [a b]))
will stay with sad-panda

borkdude18:10:43

There are more details in this clj-kondo issue: https://github.com/clj-kondo/clj-kondo/issues/916

borkdude18:10:07

(feel free to upvote the issue with a thumbs up)

seancorfield18:10:05

@U0E2268BY I'm curious about this comment: https://clojurians.slack.com/archives/C03S1KBA2/p1665337162844769?thread_ts=1665334951.512419&amp;cid=C03S1KBA2 -- do you write hobby code in a very different style to production code? I know this is a bit of a tangent from the original thread so if you want to drill down into this in a new thread or a different channel (or even via DM), that would be cool.

Alex Miller (Clojure team)18:10:37

Symbol reference is fine but you should not consider the symbols being bound in the destructuring to be in scope yet. And I wouldn’t consider it wrong for Clojure to change the map type of that :or map to a built-in map type in the service of optimization.

seancorfield18:10:03

So that's fine because... :keys is used first by destructuring to create a let binding in the same order as the symbols in the vector? What about destructurings that have multiple :keys or mix :strs or :syms? Presumably there's a much less clear ordering there -- although I would still expect each of those vectors to all be processed "first" and then any :or clauses?

borkdude18:10:11

sorry, I deleted my messages, but the gist of it was: :or can refer :keys bindings, but not bindings re-defined or new bindings in :or itself

borkdude18:10:34

due to how it's currently implemented - there are no ordering issues, but if core says this isn't even supported, I'm fine with that

seancorfield18:10:37

{:keys [a b c] :foo/keys [d e f] :or {d (inc a)}} -- is that defined behavior? (I would expect not)

borkdude18:10:40

that's indeed a tricky one

seancorfield18:10:31

user=> ((fn [{:keys [a b c] :foo/keys [d e f] :or {d (inc a)}}] [a b c d e f]) {:a 42 :foo/f 13})
[42 nil nil 43 nil 13]
user=> ((fn [{:keys [a b c] :foo/keys [d e f] :or {b (inc f)}}] [a b c d e f]) {:a 42 :foo/f 13})
Syntax error compiling at (REPL:1:57).
Unable to resolve symbol: f in this context
user=>
(edited to show :foo/f in both examples)

seancorfield18:10:10

(I'm not surprised by this but it makes me think the {d (inc a)} is also only working "by accident")

👍 2
phronmophobic18:10:24

Unpacking Destructuring seems like it would make for a great blog post. You could even cover stuff like:

> (let [[ & [ & [& [& [& [& hi]]]]]] [42]]
    hi)
(42)

> (let [[& {:as m}] [:a 42]]
    m)
{:a 42}

👀 1
🤯 1
seancorfield18:10:12

(and then there's the difference introduced in Clojure 1.11 🙂 )

Alex Miller (Clojure team)18:10:09

@U04V70XH6 if you have enough kv pairs in that map binding example earlier, the order of evaluation for those key sets may change. In short, the expression side of an :or map should not rely on locals being bound in the same destructuring map and you should not make assumptions about when those or expressions are evaluated (or even whether they are evaluated at all)

seancorfield19:10:07

@U064X3EF3 Thanks. That's exactly what I expected as far as undefined behavior is concerned and why I wanted to show the "two :keys" example, even with "small" hash maps. I think it's a good test case for clj-kondo too so I'll add it to that issue.

waffletower20:10:29

please make it a warning

waffletower20:10:44

or suggest in the issue

waffletower21:10:53

@U04V70XH6 I don't like many of the opinions found, particularly at the warning level, of linters

waffletower21:10:26

and on the weekends I really don't want to have to fight them in my code

waffletower21:10:15

linters are typically curated by an individual, and sometimes the configuration philosophy of that curator leans on the side of more work for the linter user. Since we are speaking of how I'd like to spend my spare time, I lean toward the protective

seancorfield21:10:44

Interesting. I'm always curious about people's workflows and how/why they might differ by context. I use my laptop for OSS / hobby stuff but my desktop for work so they're "physically separate" but I have my VS Code environment sync'd across the two machines so there's no "shifting of gears" between how I work and how I "play" -- with the exception of GitHub Copilot only being active on the laptop (since I'm free to use it as an OSS maintainer, but it would be a paid service if I used it for work). I used to have different editor environments set up on home and work but it was very jarring...

waffletower21:10:14

I do use the same development config, but obviously auto-linting is not a part of my workflow. Perhaps if I invested the time to get a permissive enough config, and perhaps whitelisting require '[clojure.test :refer :all] and eliminating unused symbol warnings, I could add it to my editor.

waffletower21:10:17

I do like to use linters as an advisor rather than a girdle.

seancorfield21:10:15

Cool. I love clj-kondo "nagging" me, even when I'm just playing around, as it reinforces the practice of writing "better" code, but I get that some people just don't like that much "over the shoulder critique" when they're typing 🙂

seancorfield21:10:48

(and I have clj-kondo dialed up from its defaults to be even more strict about several things)

waffletower21:10:28

The latter concern, unused symbol warnings, I feel is an important one, particularly when regarding kwargs. I tend to declare unused :as symbols, for documentation purposes, and I feel that clj-kondo is too binary about that by default. I eliminate unused symbols purposefully in other contexts, but again I prefer advisors to girdles. Error on linter warning in circleci is very irksome.

borkdude21:10:09

The :as can be configured (as can several other binding things like defmethod bindings)

waffletower21:10:29

Ya, I'll look that one up and stop complaining 😄

borkdude21:10:42

When I started clj-kondo some of those things might have been too opinionated, but nowadays all new opinionated stuff is :off by default (or should be)

borkdude21:10:57

The refer :all is opinion, but also helps clj-kondo itself to resolve vars better

borkdude21:10:05

you can put config in your home directory so it works for all your projects

borkdude21:10:28

I find (opinion, I know) :refer :all to be confusing, even in README.md of libraries that introduce the lib with examples that start with:

(require '[mylib :refer :all])
(foobar :dude :update-fn baz)
I'm like... eh where do things come from... can't you please just use an alias or :refer [...]? But yeah, that might just be me ;)

waffletower21:10:29

wow, it even says which can be useful for documentation

waffletower21:10:12

nice! I should actually read more of your documentation borkdude! Been focussed on babashka and its libraries (big fan of babashka.fs)

🙏 1
ghadi01:10:00

alex has already weighed in, but I want to reiterate that 1) :or maps do not introduce symbol bindings, and 2) mappings have no order, so :or maps have no order

ghadi01:10:25

prints a "maps have no order" t-shirt for the conj

1
💯 2
💰 2
Joerg Schmuecker21:10:11

Why is the following not passing the stest/check?

Joerg Schmuecker21:10:39

Why does the following not pass the test:

(defn plus1 [x] (+ 1 x))
(s/fdef plus1
        :args (s/cat :x number?)
        :ret number?
        :fn #(= (+ 1 (-> % :args :x)) (:ret %)))

(stest/abbrev-result (first (stest/check `plus1)))
;; => {:spec (fspec :args (cat :x number?) :ret number? :fn (= (+ 1 (-> % :args :x)) (:ret %))),
;;     :sym bank-processor.core-test/plus1,
;;     :failure
;;     {:clojure.spec.alpha/problems
;;      [{:path [:fn],
;;        :pred (clojure.core/fn [%] (clojure.core/= (clojure.core/+ 1 (clojure.core/-> % :args :x)) (:ret %))),
;;        :val {:args {:x ##NaN}, :ret ##NaN},
;;        :via [],
;;        :in []}],
;;      :clojure.spec.alpha/spec
;;      #object[clojure.spec.alpha$spec_impl$reify__2060 0x241aa6a5 "clojure.spec.alpha$spec_impl$reify__2060@241aa6a5"],
;;      :clojure.spec.alpha/value {:args {:x ##NaN}, :ret ##NaN},
;;      :clojure.spec.test.alpha/args (##NaN),
;;      :clojure.spec.test.alpha/val {:args {:x ##NaN}, :ret ##NaN},
;;      :clojure.spec.alpha/failure :check-failed}}

p-himik21:10:53

Because

user=> (= ##NaN ##NaN)
false

seancorfield21:10:10

Maybe change number? to int? assuming you only want plus1 to work on integers?

Joerg Schmuecker21:10:11

p-himik: any idea why {:args {:x ##NaN} ... } ? The spec tells it that it should be a number

p-himik21:10:37

Because

user=> (number? ##NaN)
true
:) A bit confusing, I know.

Joerg Schmuecker21:10:00

Ah, … let me try.

seancorfield21:10:54

user=> (for [n [1 1.0 ##NaN ##Inf]] (int? n))
(true false false false)
user=> (for [n [1 1.0 ##NaN ##Inf]] (double? n))
(false true true true)
user=> (for [n [1 1.0 ##NaN ##Inf]] (number? n))
(true true true true)
user=>

Joerg Schmuecker21:10:56

Thanks! That was the reason. The following works:

(defn plus1 [x] (+ 1 x))
(s/fdef plus1 
        :args (s/cat :x (s/and number? #(not (Double/isNaN %))))
        :ret number?
        :fn #(= (+ 1 (-> % :args :x)) (:ret %)))

Joerg Schmuecker21:10:54

It is a bit clunky, though.

seancorfield21:10:43

Ah, I guess (plus1 ##Inf) is still ##Inf so that passes too...

Joerg Schmuecker21:10:11

user=> (= ##Inf ##Inf)
true

Joerg Schmuecker21:10:58

So that’s no problem as the check will pass as well. It’s just that (= ##NaN ##NaN) is false. Which is weird.

p-himik21:10:44

It's similar to NULL in SQL. NULL is not equal to NULL there. NULL is unknown information, and something unknown cannot be equal to something unknown. NaN is "not a number" - and something that's not a number cannot be equal to something else that's not a number. Perhaps there's more appropriate technical or philosophical explanation, but that one for some reason got stuck in my mind.

👍 1
didibus01:10:29

Your test is not very useful though, it's just testing the same code. You'd need to use a different implementation to make it a better test. Or you'd need to test properties of +1, like some general patterns you know it should follow and assert those.

Joerg Schmuecker02:10:02

🙂 It’s a test for inc . Not very useful to rewrite in the first place. I stripped all the real logic out.

Joerg Schmuecker02:10:53

Thanks for all the help. It all makes sense now.

Joerg Schmuecker02:10:18

… and if you are confused by the fact that (= (inc ##Inf) ##Inf) then don’t read about Hilbert’s Hotel.

seancorfield02:10:27

Oh, I wasn't confused about it -- I'm a mathematician by "training" (back at university, at least) -- but when I saw the ##NaN thing, my brain went "Oh, there's another weird number in Clojure, ##Inf", and so I started to type and that was when my mathematician kicked in and corrected what I was typing somewhat :)

seancorfield02:10:39

But I did have to convince my brain that (= ##Inf ##Inf) ;=> true whereas I knew that (= ##NaN ##NaN) ;=> false 🙂

didibus02:10:42

Okay, didn't totally get what you're testing. But I just mean that (= (+ 1 x) (+ 1 x)) is not a very good way to test +, because if it has a bug the bug will exist on both side.

Joerg Schmuecker09:10:33

@U0K064KQV 🙂 agreed, not a very meaningful test at all. That’s why I knew the problem had to be somewhere else.

Brian Beckman14:10:07

if I remember correctly, NaN not equal to Nan is explicitly specified in IEEE 754. If so, Clojure / Java are just following the official spec for floating point numbers.