Fork me on GitHub
#clojure
<
2019-11-15
>
lvh03:11:51

Is there a way to get clj to print out the actually-resolved deps it's using? (I'm trying to override a dependency but it doesn't appear to working -- maybe :override-deps is an alias-only thing)

Alex Miller (Clojure team)04:11:23

it is an alias-only thing

lvh12:11:10

Thanks! I just realized how it’s supposed to be used: overriding a dependency in a specific context (represented by an alias) — I wanted to use it to override a transitive dependency, but I guess that’s just what top level :deps + exclusions was to begin with

sogaiu03:11:36

don't know if it will help but within the .cpcache dir there are files with details about deps and versions iiuc

seancorfield03:11:30

@lvh Yes, :override-deps is an alias-only thing, and -Stree should show you the final, selected versions of everything.

lvh03:11:47

Gotcha, thanks! I found -Stree but I wasn't sure if that was post-resolution.

rgm04:11:13

So my colleague and I head-scratched our way through fixing a situation, and we want to give back a document, or some code, or a plug-in, or what have you, and we aren't sure where'd be best.

rgm04:11:16

We're delivering a skinny jar as a "free" version of a bigger SaaS product, and so as not to give away everything we AOT'd our clojure code into a jar. First we were using straight-up compile and the jar command, and now we're using badigeon. We hit https://clojure.atlassian.net/browse/CLJ-1544 where we have to figure out which protocols to also AOT.

rgm04:11:01

(We went with skinny and a pom.xml for the rest of the deps to keep the initial download small, and to play it paranoid-safe on bundling/licensing/etc.)

rgm05:11:49

anyway, it seems like quite a rough edge and we'd like other people to benefit from it, and so I'm asking advice on what'd be the best way to get this out there ... blog post? Plug-in of some sort? Contribution to the AOT guide?

rgm05:11:45

if it's possible to introspect a set of namespaces to extract a set of references to protocols (so we know which ones to include in the jar) I can see us making up a library or helper too.

rgm05:11:13

I suppose another idea would be to implement the proposed fix in CLJ-1544 but as a lib or contribution to badigeon.

seancorfield05:11:51

@rgm AOT is a thorny problem and the general advice is to only AOT whole applications, as the last step before deploying them, and to avoid AOT'ing libraries at all costs. At work we don't even AOT our applications -- it's just not worth the hassle.

seancorfield05:11:17

If your product is SaaS then maybe making a full JAR and AOT'ing all of it, so it could be run directly (but could also still be used like a library, much like REBL from Cognitect), would be a safer approach?

seancorfield05:11:56

Writing it up on http://ClojureVerse.org might be a nice way to put the information/process out there for the community, as well as generate some follow-up discussion -- or on your own/your company's blog and then posting a link and summary to ClojureVerse?

rgm05:11:42

yeah, maybe our own blog is the way to go, if only so we actually lock down our own understanding.

rgm05:11:36

as far as I can tell we successfully managed a thin AOT'd jar, and it was certainly educational, but yeah, not sure I'd try that again.

seancorfield05:11:51

The problem with AOT'd libraries is that you often end up tied into specific versions of your dependencies (including Clojure itself) so the library can be very brittle -- and may break with any change in the dependencies used by clients of your library.

seancorfield05:11:03

I'll be interested to read your blog post @rgm and to hear any experience reports from folks who try to use your "thin AOT'd jar" but I expect pain 🙂

🤕 4
rgm06:11:55

well, I expect some travails but fortunately it's an application and not a lib.

seancorfield06:11:01

Then I'm confused about why you refer to it as a "thin JAR" -- applications are "uber JAR" by definition @rgm and AOT'ing those, AOT'ing all namespaces, is fine (like I say, like Cognitect's REBL).

rickmoynihan09:11:12

Is that really true for REBL? I always felt half of the problems associated with using REBL are down to how it is AOT’d. (The other half are down to JavaFX/bundling and JVM versions.) The AOTing is fine when it’s used as an application; but when it’s used in a project with other dependencies on the classpath isn’t it problematic? e.g. there are a bunch of weird hacks that I’ve had to do with REBL to get it to work, like including deps it has that should be transitive… e.g. snakeyaml / data.csv / data.json… I always suspected that was because it was AOT’d and effectively being used as a library.

rgm15:11:55

Oh, it’s more than possible I’m using the wrong terminology. OK, the distribution is a tgz, with our semi-uberjar and a list of deps in a pom.xml file. To run the app from our jar, you’d need to pull down the deps listed in the pom.xml, and then java -cp "our.jar:lib/* …`

rgm15:11:29

(I’m coming to appreciate how weird this is … it makes things much more like a py/ruby/js app distribution and sets us up for some of the same left-pad-style weirdness.).

rgm15:11:11

though our instructions to yank the supporting jars into a local lib dir gives us much the same benefit as pyenv / rbenv / nvm in terms of isolation.

Sandae Macalalag07:11:44

What's the best way to convert unix epoch to date time?

Sandae Macalalag07:11:10

It's giving me bad result

seancorfield08:11:51

@sandae.macalalag What "date time" type are you trying to produce?

seancorfield08:11:54

(and I don't see what "bad result" you are getting from that screen shot -- please post actual code and results as text)

Sandae Macalalag08:11:19

Oh, the return date is "1970"...

Sandae Macalalag08:11:41

(require '[clj-time.core :as t])
(require '[clj-time.coerce :as c])
(c/from-long 1573788135)
#clj-time/date-time "1970-01-19T05:09:48.135Z"

Sandae Macalalag08:11:46

If I convert it from the epochconverter it's giving me November 15, 2019

seancorfield08:11:17

user=> (java.time.Instant/ofEpochSecond 1573804980)
#inst "2019-11-15T08:03:00Z"
user=> 

seancorfield08:11:51

Don't use clj-time. Joda Time is deprecated. You should use Java Time instead -- as it says on the readme of clj-time.

Sandae Macalalag08:11:13

Ah! Thank you sir! and for your awesome tools.

2
seancorfield08:11:33

Also, per the docstring for clj-time.coerce/from-long it expects milliseconds since the epoch, now seconds.

👍 8
seancorfield08:11:16

(! 836)-> clj -Sdeps '{:deps {clj-time {:mvn/version "RELEASE"}}}'
Downloading: clj-time/clj-time/maven-metadata.xml from 
Clojure 1.10.1
user=> (require '[clj-time.coerce :as c])
nil
user=> (c/from-long (* 1000 1573804980))
#clj-time/date-time "2019-11-15T08:03:00.000Z"
user=> 

Sandae Macalalag08:11:49

I guess, I'm going to say it now from my current perspective. JavaScript has huge adoption because there are a lot of tools and blog posts around where you can find what you're looking for... Clojure's future seems bleak because there aren't a lot of code/tools and blogs in the wild. It's really hard for beginners to traverse. Clojure does it's job very well though. I hope more and more people will use it and showcase what they do. I will add my fair share as well. Writing posts about my journey.

seancorfield08:11:02

Clojure isn't trying to be "popular". Just "really good". But it is definitely a language that is geared more to experienced developers who are tired of the problems that other "popular" languages bring to the table and want something better.

👍 4
seancorfield08:11:50

And, yes, it is hard for beginners to learn, especially if they're coming from a world of OOP and/or mutable state and assignments etc. The same is true of Elm and Haskell and PureScript and several other (mostly) pure functional languages. Because it requires a different way of thinking. Complete beginners -- who've never programmed before -- often pick up FP a lot more easily than junior devs who know Java/JavaScript.

etiago08:11:43

And about the future looking bleak, Elisp appeared in 1985 and whilst perhaps your common dev won't know it, Emacs plugins are written in it 🙂 ... I agree that it's not one of those languages that will (probably ever) be mainstream, and that's fine.

👍 4
vlaaad08:11:44

being forced to write some java currently at work, I'd say clojure feels more beginner-friendly

👍 4
seancorfield08:11:50

(my opinions on Java are not very charitable so I will refrain from sharing them here)

seancorfield08:11:06

(my opinions on JavaScript are even less charitable 🙂 )

😂 4
4
Sandae Macalalag08:11:53

I would like to hear that!

seancorfield08:11:01

"Clojure and its communication forums are run by, and for, people who make things." -- https://www.clojure.org/community/etiquette

Sandae Macalalag08:11:29

Oh. Thanks for the link.

vlaaad08:11:49

fast feedback loop with REPL + autocompletion/static analysis from Cursive is better than slow feed back loop with compile/run/reach state you are interested in + autocompletion/static analysis by IDEA java plugin

zilti09:11:56

It is a pity that this isn't bridged to Matrix

zilti09:11:56

Anyway, I have a problem in that clojure.core/compile is not working for me. What I get is `Syntax error (IOException) compiling fn* at (compiletestfile.clj:1:1). Datei oder Verzeichnis nicht gefunden`

vlaaad09:11:21

does "classes" directory exists and on a classpath?

zilti13:11:39

Ah, no, it does not. I assumed the compile would create that as needed

rickmoynihan13:11:25

how do java devs find libraries these days?

rickmoynihan13:11:42

google/github/mvn search?

Alex Miller (Clojure team)13:11:31

they copy the pom.xml of the last project

4
Alex Miller (Clojure team)13:11:06

but seriously, those would be my guess

mping13:11:01

I learn about some stuff in some companies' technology radars

mping13:11:06

other stuff in hacker news

mping13:11:18

other stuff in techempower's benchmarks

mping13:11:20

and so on

rickmoynihan13:11:44

I’m trying to see if anyone has implemented a java.nio.file.FileSystem Provider that is backed by a local directory… this is my most promising find so far: https://github.com/peter-mount/filesystem

eraserhd14:11:26

And TIL about java.nio.file.FileSystem, neat.

eraserhd14:11:57

@rickmoynihan what's the use case?

rickmoynihan14:11:23

for that class or backing by a local directory?

rickmoynihan14:11:05

Main use case is abstraction… I’d like the code I write to work with a zipfile or a local directory or potentially an s3 bucket.

Jakub Holý (HolyJak)14:11:38

Update: The problem has been fixed, having `Clojure.read("'minbedrift.auth")` was wrong. The code was updated as suggested. Hi! What are best practices for exposing Clojure functions to Java? I have created a Java class Auth with a static method calling clojure like this (notice the require):

public class Auth {
    private static final IFn authTokenFn;
    static {
        Clojure.var("clojure.core/require").invoke(Clojure.read("minbedrift.auth"));
        authTokenFn = Clojure.var("minbedrift.auth/auth-token");
    }
    public static String getAuthToken() { return (String) authTokenFn.invoke(); }
}
but clojure.lang.RT.load fails at runtime with > FileNotFoundException: Could not locate 'minbedrift/auth__init.class, 'minbedrift/auth.clj or 'minbedrift/auth.cljc on classpath. But I see the file in the jar:
$ unzip -l build/libs/clj-common-0.1.0-SNAPSHOT-all.jar | egrep 'minbedrift/(Auth|auth)' 
     1299  11-15-2019 15:26   minbedrift/Auth.class
     2196  11-15-2019 15:26   minbedrift/auth.clj
How is it possible the class cannot find a file just next to it? Is require the wrong call to make? Should I use one or the load* functions instead? Thank you!

4
mping14:11:47

whats the static initialization order? first the static block?

mping14:11:56

can you run line by line to know whats the line thats throwing?

Alex Miller (Clojure team)14:11:04

you don't need to ' in that code

Alex Miller (Clojure team)14:11:32

you should do the authTokenFn init in the static {} block after you require

Alex Miller (Clojure team)14:11:55

I don't think either of those correspond to the error though

Alex Miller (Clojure team)14:11:58

require should be fine here

Alex Miller (Clojure team)14:11:56

I guess I would check in your Auth initializer whether you can do Clojure.class.getClassLoader().getResource("minbedrift/auth.clj")

Jakub Holý (HolyJak)15:11:17

Thanks! I have updated my post to indicate that the problem is fixed and the cause.

Daniel Stephens15:11:27

(do 
  ;; random spec that exists
  (s/def ::unrelated string?)
  
  ;; spec for a map that I don't care about the contents
  (s/def ::map-of-who-cares (s/keys))
  
  ;; this returns false, is this expected? Seems like a bug to me
  (s/valid? ::map-of-who-cares {::unrelated 1}))
Is this intended, seems like a bug to me?

ghadi15:11:15

spec'ed attributes are always checked @dstephens

😭 4
Daniel Stephens15:11:46

Thanks for the link

ghadi15:11:23

(s/valid? (s/keys) {::unrelated 1}) will show the same result -- this design is intentional

Daniel Stephens15:11:55

I mean, I would expect those to be consistent but seems there isn't a way to spec a map that only validates on the things I care about at the time. `(is (= true (s/valid? (s/keys :req [::the-actual-key-i-want-validated]) {::the-actual-key-i-want-validated "valid-value" ::unrelated 1})))` Also this kind of implies that specifying :opt is completely pointless?

andy.fingerhut16:11:00

IIRC, :opt is useful for random data generation for testing.

👍 4
andy.fingerhut16:11:29

spec isn't only about checking values, but also randomly generating them.

borkdude15:11:38

If I would users give access to a limited set of classes, say String in an interpreter, can they, via chained method calls, do harmful things, like (System/exit 0)?

borkdude15:11:23

I could build in an extra check if the method call still happens on the "allowed" classes, but I want to know if this is necessary

borkdude15:11:33

I'll probably include it, just for safety

Azrea16:11:36

So, if you let them get attributes on the class, they can do something like String.class.getClassLoader().defineClass(...), which gives them arbitrary bytecode execution. If you're careful with whitelisting in terms of allowed classes, values, and methods, you might be good though.

rickmoynihan16:11:52

What’s the easiest way to write an anaphoric macro that creates a resource binds it a symbol/name of the users choice, and releases it afterwards? e.g. from the user perspective I’d like:

(with-temp-file temp-file
    (println temp-file))
It’s been a while since I’ve written one of these; but my usual tricks of resorting to ~'sym and gensyms sym# to avoid the ns qualified symbols of syntax-quote don’t work; because I also need to rewrite the form in & body. I’m pretty sure in the past I resorted to either a custom syntax quote implementation to do this sort of thing or a postwalk-replace of form. Are there easier ways?

wotbrew16:11:18

Maybe I'm being dumb but:

(defmacro with-temp-file 
  [sym & body]
  `(let [~sym (println "creating thing")]
     (try 
       ~@body
       (finally (println "releasing-thing")))))

lukasz16:11:30

and error handling

rickmoynihan16:11:10

hmm that was essentially the first thing I wrote; but it was failing at the time because the symbol in the let expansion wasn’t a simple-symbol?

rickmoynihan16:11:34

though now it seems to be working — must’ve missed something… 👀

rickmoynihan16:11:08

ok weird, I’m not seeing the same error now

rickmoynihan16:11:59

thanks @lukaszkorecki I clearly did something stupid and didn’t notice

👍 4
rickmoynihan16:11:54

hmmm looking at this further it is weird that

(macroexpand-1 `(with-temp-file foo (println foo))) ;; =>
(clojure.core/let
                              [my.ns/foo (my.ns/create-tempfile)]
                            (clojure.core/println my.ns/foo)
                            (my.ns/delete my.ns/foo))
Yet taking that expanded form and evaluating it directly results in the spec error:
Syntax error macroexpanding clojure.core/let at (*cider-repl repos/muttnik:localhost:51301(clj)*:850:43).
my.ns/foo - failed: simple-symbol? at: [:bindings :form :local-symbol] spec: :clojure.core.specs.alpha/local-name
my.ns/foo - failed: vector? at: [:bindings :form :seq-destructure] spec: :clojure.core.specs.alpha/seq-binding-form
my.ns/foo - failed: map? at: [:bindings :form :map-destructure] spec: :clojure.core.specs.alpha/map-bindings
my.ns/foo - failed: map? at: [:bindings :form :map-destructure] spec: :clojure.core.specs.alpha/map-special-binding

vlaaad16:11:52

use ' not ` for macroexpand

rickmoynihan16:11:32

doh! Thanks. It’s been a while.

emccue18:11:42

Would it be "okay" to have something with interior mutability inside an agent

emccue18:11:56

With the assumption that actions never fail

emccue18:11:34

And is it safe to do arbitrary side effects inside some action put upon an agent

emccue18:11:47

(for the longest time I've just been okay using atoms so agents and refs still elude me a bit)

andy.fingerhut18:11:37

The documentation page on agents I link says this: "The state of an Agent should be itself immutable (preferably an instance of one of Clojure’s persistent collections), and the state of an Agent is always immediately available for reading by any thread (using the deref function or reader macro @) without any messages, i.e. observation does not require cooperation or coordination." https://clojure.org/reference/agents

hlolli18:11:00

I would say it's ok, but it's a bad habit. You'll miss some logs and debugging is going to be hard.

andy.fingerhut18:11:01

Not sure if that addresses your question about "interior mutability inside an agent".

hlolli18:11:09

I'm making a gen-class, and it seems to me that the reflector isn't finding the overridden method of superclass I have :exposes-methods {"getCurrentContext" "getCurrentContextSuper"} and I call (.getCurrentContextSuper this), and the reflector refuses to recognize getCurrentContextSuper in this.

andy.fingerhut18:11:45

I don't see anything in the docs that say whether all actions for the same agent can be executed in different threads, or are guaranteed to be executed in the same thread. I would assume different send action functions might be executed in different threads, so if any of the internal state of the agent is mutable, you would be reponsible for ensuring that the mutable state was properly synchronized between the threads.

andy.fingerhut18:11:43

and you'd be doing so explicitly against the recommendations in the documentation, so not on terribly solid ground in doing so.

hlolli18:11:09

I wrote atoms once inside an agent, because I used the agent as a thread and used the atom to signal the state. It was bad in every way, it didn't crash and everything worked fine. But the value of the atom was a complete black box.

andy.fingerhut18:11:29

But at least changes to an atom are safely published to all threads that try to deref them, so you were probably on reasonably safe ground in terms of the Java memory model, at least, avoiding data races.

Alex Miller (Clojure team)18:11:49

You can do side effecting things in agent actions and in fact because the stm is agent-aware (agent sends in a txn are held till the transaction completes), this is a pretty useful hook

Alex Miller (Clojure team)18:11:03

I would heed the warning to use immutable data as agent state but you could have nil as the state and (safely) update other state, either by using a state construct, locking, etc

andy.fingerhut18:11:59

Does the implementation guarantee safe publishing of data mutated in an execution of an action function to all other threads somehow?

Alex Miller (Clojure team)18:11:34

Explicitly, no, I don’t think so

Alex Miller (Clojure team)18:11:09

Practically, maybe, but just don’t do that

andy.fingerhut18:11:13

So swap! on an atom is safe, as are many other kinds of mutations, by their implementations, but a lot of arbitrary mutations of Java objects would be unsafe, I think.

etiago19:11:00

I've started playing with macros. Oh. My. God. This changes everything :shocked_face_with_exploding_head:

🚀 4
etiago19:11:36

I've written a single-future macro that puts the future in an atom, in a concurrent-safe way such that only one future is ever running for that piece of code.

etiago19:11:42

(defmacro single-future
  [future-atom & args]
  (list
   'swap!
   future-atom
   (list 'fn '[old]
         (cons 'do
               (list
                (list 'when (list 'future? 'old) (list 'future-cancel 'old))
                (list 'future (cons 'do args)))))))

etiago19:11:20

#roastmycode 😄

Alex Miller (Clojure team)20:11:10

back tick would clean that up a lot

etiago20:11:01

I believe you. I still need to brush up on back tick...

etiago20:11:30

This was written very much holding the handrail as I went along 😄

Alex Miller (Clojure team)20:11:29

(defmacro single-future
  [future-atom & body]
  `(swap! ~future-atom
     (fn [old#]
	   (do
         (when (future? old#)
           (future-cancel old#))
          (future ~@body)))))

thanks 4
Alex Miller (Clojure team)20:11:19

I don't think that needs to be a macro, could just be a function

Alex Miller (Clojure team)20:11:25

the shape would be a bit different

etiago20:11:40

Wouldn't the body be evaluated as args unless it's a macro, and defeat the purpose? :thinking_face: Thinking out loud here...

Alex Miller (Clojure team)20:11:43

you could put it in a fn and call future-call or something like that

etiago20:11:21

Hmm good point 🙂

Alex Miller (Clojure team)20:11:09

it wouldn't be as pretty, but I don't think there's any reason it couldn't be a function

jaide20:11:11

Out of curiosity, is this a situation where we should say, "Well this could be a function so it should be a function"? Or is the macro justified by the value in a more ergonomic interface?

etiago21:11:33

I confess I still struggle to find situations where only a macro fits, where using a function just isn't possible (do those actually exist?). And with my basic understanding, aren't built-in macros like with-open technically possible to write as functions?

andy.fingerhut21:11:47

If you want to create a construct that conditionally evaluates some of its arguments, e.g. like if or cond do, that is easier with a macro. I think it is only possible to implement as a function if all of the conditionally-executed things are wrapped inside of functions.

andy.fingerhut21:11:25

It isn't easy to see how one would create something with local bindings, like let, without a macro.

noisesmith21:11:35

one very pithy way to put it is that macros are for making new syntaxes (where syntaxes are ways of defining conditions for evaluation or local bindings etc.) - macros can also create namespace level bindings via def / defn which is problementic vis functions, and generally make things that delegate to other macros that treat symbols in a special way that doesn't work for function indirection

jiriknesl21:11:39

I found even when macros are great, application developer barely needs them. Most of the time, they are for library developers and only in special cases (like adding pattern matching, core.async, etc.)

noisesmith21:11:29

right - cases where you wrap something else that is a macro, so effectively "contagion" of macros

etiago21:11:59

I wonder if it can even become confusing... If a function in a library is actually a macro and it's not extremely obvious from the documentation, whoever's using that function might be blindsided by the args not evaluating at the expected time. Maybe. :thinking_face:

etiago21:11:35

On the other hand, in the concrete example I created, I'm deriving a special form of a future, and future in itself is a macro. If I was to release my code, it could make sense to keep it as a macro for reasons of developer familiarity, even though it doesn't have to be one.

Alex Miller (Clojure team)21:11:26

as is somewhat common, future is a macro built over the top of a function, future-call

Alex Miller (Clojure team)21:11:02

this is a common pattern, because it's pretty useful

Alex Miller (Clojure team)21:11:12

it's not applicable in every case of course

Alex Miller (Clojure team)21:11:51

here if your goal really is to provide something future-like but with differing capabilities, then I agree I would make it a macro to match the syntax expectations of future users

etiago21:11:25

Basically I started seeing a pattern in my code: I had background loops which I wanted to only ever have 1 thread going over that particular piece of code. The way I was doing this was by putting futures in atoms with the code above, but it's quite a bit of cruft to have around existing business logic. So I decided this would be my first real venture into macroland 😄

noisesmith21:11:37

another angle here in case no-one mentioned it, future-cancel only works on a specific set of methods, and can't generally be relied on

noisesmith21:11:12

in your case if future-cancel was called, and the active code wasn't cancellable, you would quietly create a second future, or more too the point, create a second future without the first exiting

etiago09:11:21

That's a fair point. I thought about this, and I couldn't come to a conclusion on what I'd want to happen in case the future isn't cancellable/does not cancel, so I chose the default behaviour of "sticking my head in the sand" and ignoring it 😄 . But yeah, there's potential for bad behaviour.

borkdude21:11:08

Just to make sure, is reduce-kv single-threaded or can it use multiple threads ?

Alex Miller (Clojure team)21:11:58

clojure.core.reducers/fold is really the only parallel reduction in Clojure

borkdude21:11:31

I've got a weird issue here:

(defn normalize-classes [classes]
  (let [sym->class (transient {})
        class->opts (transient {})]
    (reduce-kv (fn [_ sym class-opts]
                 (let [[class class-opts] (if (map? class-opts)
                                            [(:class class-opts) class-opts]
                                            [class-opts {}])]
                   (prn "SYM" sym (type sym) class (type class)) ;; outputs: "SYM" java.lang.ArithmeticException clojure.lang.Symbol java.lang.ArithmeticException java.lang.Class
                   (assoc! sym->class sym class)
                   ;; storing the physical class as key didn't work well with
                   ;; GraalVM
                   (assoc! class->opts sym class-opts)))
               nil
               classes)
    (let [sym->class (persistent! sym->class)]
      (prn (contains? sym->class 'java.lang.ArithmeticException)) ;; false, huh?
      {:sym->class sym->class
       :class->opts (persistent! class->opts)})))
On line 8 it prints a symbol that it's going to add using assoc! to the transient map. On the third line from the bottom it prints false which indicates it wasn't added...

csm21:11:14

assoc! on a transient map can return a new map — it doesn’t necessarily update it in place

borkdude21:11:42

ah, that was my assumption. thanks

andy.fingerhut21:11:37

sometimes transients do mutate the Java object given, in place, so people easily make the wrong inference from small experiments in the REPL.

borkdude21:11:33

now it works, phew... thanks @csm301

bfabry22:11:42

yeah they're not meant to be used like variables. they're meant to be used like normal data but with a big loud asterisk that they're not always (or even usually) persistent

bfabry22:11:58

hence with assoc! returns a new transient

csm22:11:59

this has bitten me a few times — I think transient vectors and sets don’t have this behavior

csm22:11:16

I meant the current implementation doesn’t — at least I’m pretty sure that’s the case. But yes, it’s not correct to treat it like it’s update-in-place

andy.fingerhut22:11:42

If we had a Rust borrow checker, it could catch this mistake, but that is asking a lot.

hiredman22:11:55

the transient docs say something to the effect of "don't bash in place"

andy.fingerhut22:11:04

I'm not seriously suggesting trying to change Clojure in any way like this 🙂

bfabry22:11:51

actually @borkdude you've discovered a great lint check here. any time someone doesn't use the return value of conj! pop! assoc! dissoc! disj! it's probably an error

👍 8
andy.fingerhut22:11:34

(cough -- eastwood does -- cough :-)

🙂 8
andy.fingerhut22:11:31

at least I am pretty sure it does, among warnings that other functions that should always have their return value used, not ignored.

borkdude22:11:49

cool! I'll also consider it for clj-kondo then

andy.fingerhut22:11:10

It uses that big map of data you borrowed and enhanced, @borkdude, IIRC

borkdude22:11:02

Ah, nice, thanks for the pointer.

andy.fingerhut22:11:52

Same warning applies if key :pure-fn has value true, too. conj! is not a pure function, so I created separate keys for those two properties.

borkdude22:11:02

There was already an issue for this (unused constant/pure values), but now it's one extra puzzle piece falling in the right place

andy.fingerhut22:11:37

And please take my (cough - eastwood does) comments as me attempting to be mildly funny. I am perfectly happy if clj-kondo becomes better in every way than Eastwood ever got.

borkdude22:11:15

This was an older issue about more or less the same: https://github.com/borkdude/clj-kondo/issues/298

emil0r22:11:22

Is there a good way to select keys in a map based on a namespace? E.g. you can specify #:foo{:bar 1 :x/y 2 :baz 3} and then use (select-keys my-map [:foo/bar :foo/baz]) to get those keys. Any way to use (select-keys my-map [#:foo]) to select all keys undernead the namespace :foo ?

hiredman22:11:47

group-by namespace

hiredman22:11:40

which is to say, you will need to write it yourself, and group-by namespace is likely a quick start to that

emil0r22:11:07

Hmm.. yeah I see that

bfabry22:11:35

cljs.user=> ((group-by #(namespace (key %)) {:foo/bar 1 :foo/baz 2 :baz/bar 9}) "foo")
[[:foo/bar 1] [:foo/baz 2]]