Fork me on GitHub
#clojure
<
2020-07-28
>
Roy11:07:54

Just started #community-projects to curate projects that some of us from this group have going. I have been looking for projects to collaborate on so this would be cool for others on the same boat. 🚀

Roy11:07:35

Anyone currently looking for collaborators on their project?

borkdude12:07:23

@sveri have you seen https://github.com/ajoberstar/ike.cljj? It has some stuff around Path

sveri15:07:22

No, I have not so far, thank you. Hopefully I won't need it. The io/file problem was the first time I encountered something similar, I hope it stays so 🙂

kwladyka13:07:57

What is the simplest way to run f, n (100) times parallel? The f is making HTTP request and I want to test server response in easy way. I don’t want to spend to much time on that now to research some tools.

kwladyka14:07:23

pcalls looks ok

kwladyka18:07:39

ach no, they are limited to X at the same time, probably to the pool size

sandbags13:07:11

Since installing OpenJDK14 (was using JDK8) I now have to use LEIN_USE_BOOTCLASSPATH=no lein repl or I get an exception that I can't decipher (https://gist.githubusercontent.com/mmower/a01d6803eeb429c05676e2339676cada/raw/062e7b675d3a52f6e457d9781b939a4f66fd410c/gistfile1.txt) which seems to occur in Fipp but, from my searching, does not implicate Fipp. Is this normal? Should I expect to use this from now on? I confess I couldn't follow the discussion about what's changed/broken.

Alex Miller (Clojure team)13:07:55

yes, you should expect this from now on. imho, lein should never have been using bootclasspath in the first place, and the ground is shifting in the jdk

sandbags14:07:04

Thanks for the confirmation Alex.

Alex Miller (Clojure team)13:07:37

this was always just an abuse of bootclasspath solely to shave a few ms off startup time

vemv13:07:18

https://dev.clojure.org/jira/browse/CLJ-2426 is an occasional pain point for me, especially when interacting with 3rd party libraries that don't particularly use / are aware of metadata-based protocol extension It seems a quite simple bug to tackle, and also a frustrating one because it means that a newer, useful feature will be sort of a "second-class citizen". One would expect clojure.core features to play well with each other, offering a consistent experience It doesn't seem consistent to offer a feature but not to support it in some places?

Alex Miller (Clojure team)13:07:09

this is not a simple bug and not even something that we're sure we want to do

vemv13:07:29

I'd appreciate if these considerations were shared somewhere For example I take https://clojure.org/reference/protocols#_extend_via_metadata at face value - it offers me a feature, I use it. Maybe it's meant to be used very occasionally? Maybe it has some complexities that CLJ-2426 made evident? (So far I haven't come to actually think this. I have happily used both the feature and my own satisfies? with an extra check for IMetas. Sadly I cannot convince the world to use my own satisfies?)

Alex Miller (Clojure team)14:07:18

in the case of external or direct extensions there are single categoric things we can check to answer the question of satisfies? (either an entry in the protocol data or instanceof Java checks). with metadata extension, you have to look up the protocol methods and check whether one? all? are present in the metadata, and you need to do this check before external extensions, so it has a potentially large performance cost to add this check (which is an existing separate issue)

Alex Miller (Clojure team)14:07:59

in general, leaning on satisfies? too much is usually a smell - you should instead be leaning on protocol polymorphism to handle conditional cases

vemv14:07:14

agreed. Typically my problem comes from other libraries using satisfies?

Alex Miller (Clojure team)14:07:11

I added these comments to the ticket too

👍 3
Alex Miller (Clojure team)14:07:04

you can only use metadata extension if the protocol is declared to support it so this is not even an option in most cases

vemv14:07:38

> so it has a potentially large performance cost to add this check (which is an existing separate issue) (what's the separate issue?) One can consider that satisfies? is an occasional tool and a pretty unlikely one to belong to high-performance code paths. If one was concerned with performance, I reckon one wouldn't use satisfies? to begin with? Favoring clojure's dispatch system instead

vemv14:07:20

I realise my input is limited here, but one can consider the tradeoff of improving the performance of a function that has been 'slow' for 10 years vs. having a consistent feature set As you say, leaning on satisfies? too much is usually a smell so making it the deal breaker seems a bit odd

Alex Miller (Clojure team)14:07:27

going back to my original comment, the status of this is "not sure what we want to do"

Alex Miller (Clojure team)14:07:17

which is why the ticket is open, not closed

👍 3
wombawomba14:07:57

Is there a convenient way to get clojure.test to generate human-readable strings?

wombawomba15:07:18

I guess I’ll have to do something like

(with-redefs [gen/string-ascii (gen/fmap my-string-generator ...))]
  ...)

Ivar Refsdal14:07:24

I wrote a (small) function to cache a function for a number of seconds (as defined by the function). Does Clojure (or a lib) already have this? Here is the code: https://gist.github.com/ivarref/a9592d3ef4b3fb6c03c6ca8cd2c3c210 Any thoughts or comments? Thanks.

Ivar Refsdal14:07:27

Right thanks. But I think there you must set TTL up front when creating the cache, that is TTL may not vary

vemv14:07:43

yes noticed that :)

Ivar Refsdal14:07:58

For my case I'd like to be able to dynamically specify ttl

👍 3
vemv15:07:34

impl looks reasonable, maybe I wouldn't use locking since it's plausible that cached fns could invoke each other, causing a deadlock Maybe you use locking for not hitting a rate-limited API? In which case I'd tackle that separately

noisesmith15:07:32

I think locking creates a re-entrant lock, but yes it's better to avoid locking in general

emccue20:07:56

Personally I don't quite understand core.cache that well, so I usually default to using something like caffiene

(defn cache-function [f minutes-to-cache]
  (let [cache (-> (Caffeine/newBuilder)
                  (.expireAfterWrite (Duration/ofMinutes (long minutes-to-cache)))
                  (.build (reify CacheLoader
                            (load [_ args]
                              (apply f args)))))]
    (fn [& args]
      (.get cache args))))
But that doesn't support per-entry ttl either https://github.com/ben-manes/caffeine/issues/114

emccue20:07:40

oh, nvm it is possible

emccue20:07:43

Caffeine.newBuilder()
    .expireAfter(new Expiry<Key, Graph>() {
      public long expireAfterCreate(Key key, Graph graph, long currentTime) {
        return (graph instanceof NullGraph)
            ? TimeUnit.MINUTES.toNanos(1)
            : TimeUnit.MINUTES.toNanos(10);
      }
      public long expireAfterUpdate(Key key, Graph graph, 
          long currentTime, long currentDuration) {
        return currentDuration;
      }
      public long expireAfterRead(Key key, Graph graph,
          long currentTime, long currentDuration) {
        return currentDuration;
      }
    })
    .build(key -> createExpensiveGraph(key));

emccue20:07:00

(this is java, but you can see the interface you need to impl)

emccue20:07:48

(defn cache-single-arg-function [f]
  (let [cache (-> (Caffeine/newBuilder)
                  (.expireAfter
                    (reify Expiry
                      (expireAfterCreate [_ {:keys [ttl]} _ _]
                        ttl)
                      (expireAfterUpdate [_ _ _ _ current-duration]
                        current-duration)
                      (expireAfterRead [_ _ _ _ current-duration]
                        current-duration)))
                  (.build
                    (reify CacheLoader
                      (load [_ k]
                        (f k)))))]
    (fn [k]
      (.get cache k))))

emccue20:07:22

That should work @UGJE0MM0W This is the library i am using [com.github.ben-manes.caffeine/caffeine "2.8.4"]

Ivar Refsdal13:07:07

Thanks for all the input! @U45T93RA6 Re locking: yes, the idea was that for my case when a token expires, I'd rather not have multiple invocations of the "expensive" and cached function in a burst

Ivar Refsdal08:09:17

I ended up rolling my own mini library: https://github.com/ivarref/memoize-ttl

quadron16:07:45

let's say there is a package called x. now a process imports x and two other packages that depend on x (possibly different versions of x). will there be three separate instances of x running? will they be qualified through different namespaces/classpaths?

Vishal Gautam16:07:15

first step is to make sure that they are both using the same version

noisesmith16:07:55

it's impossible to have two versions of x loaded, without jumping through hoops (unless using js / cljs)

lilactown16:07:30

there’s some caveats here, but, essentially you can only have one version of a lib at one time

quadron16:07:54

so which version is chosen if there is ambiguity? the latest one?

quadron16:07:41

does it even choose? or does one mask the other?!

noisesmith16:07:10

the job of your package manager is to pick one

noisesmith16:07:16

that's the primary thing it does

noisesmith16:07:41

lein, deps.edn, maven, etc. each have their own set of rules, and have ways of overriding those rules

noisesmith16:07:54

outside of pathological conditions, or finicky special cases, there's no masking

quadron16:07:15

they behave differently?

lilactown16:07:31

I googled around and I found this quote: > Leiningen and Maven, when there is a conflict always pick the version that is closest to the root of the dependency tree; where as `tools.deps` always picks the newest.

noisesmith16:07:47

leiningen special cases clojure.core also

noisesmith16:07:14

leiningen also lets plugins manipulate the dep tree (this can include overriding or removing deps)

lilactown16:07:33

tools.deps also allows exclusions etc.

noisesmith16:07:00

deps.edn has profiles, but that only allows merge-over and accretion IIRC - you can't exclude in a profile something that was present in the base(?)

lilactown16:07:26

that might be true yeah

quadron16:07:35

does it mean then that under the default behaviour of deps, a package might break if there is a newer version of its dependencies called by the neighbouring packages?

seancorfield16:07:31

You mean if something else in your project is updated and now relies on a newer version of that shared dependency? Yes, that's possible. And it's possible with Maven-based resolution too.

seancorfield16:07:00

If you have a top-level dependency on it, that version takes precedence in both Maven and t.d.a. resolution tho'.

☝️ 3
seancorfield16:07:14

(if I'm understanding your scenario correctly)

✔️ 3
noisesmith16:07:06

in practice this happens with the jackson library

☝️ 3
noisesmith16:07:29

often enough that I've in many cases migrated away from libraries because they use jackson

sveri19:07:25

jersey/jackson has literally become a trigger word in our team. Now one of our runtimes decided to upgrade from jersey or jackson 1 to 2. It turns out, one of the reliant components cannot do the upgrade in time, so the runtime will deliver both. Ah and did you know that http://apache.commons.io in it's current 2.6 version has separate versions for it's packages that go down to 1.4.x? /sorry for the rant

noisesmith21:07:16

I can relate, I think we've all seen it

noisesmith17:07:21

it's very rare in the clojure world to see this sort of breakage from anything outside jackson though

plins19:07:12

hello everyone, simple question: Im asserting stuff in test, basically comparing two maps, the on from sut and the other one is the expected value is there a idiomatic way of checking if on map contains the other? what I want to avoid is breaking that test when sut adds another key to the result

Alex Miller (Clojure team)19:07:35

I have a submap? function I often use in tests

Alex Miller (Clojure team)19:07:34

there's a lot of potential choices about how this works, and I'm not sure I've made the same identical ones every time I've used it, but maybe something that will ultimately end up in core or something

Alex Miller (Clojure team)19:07:15

feel free to borrow and modify as you like

plins19:07:56

thanks everyone 😄 much appreciated

noisesmith21:07:10

the version I usually use (in unit tests in particular) is (= (select-keys m2 (keys m1)) m1)

Alex Miller (Clojure team)21:07:17

I usually wanted it for nested maps too though

noisesmith21:07:21

oh - that's different, yeah

ccann21:07:07

I'm working on an inherited codebase and I found some code for peeking/popping a Queue that is summarized in this StackOverflow answer: https://stackoverflow.com/a/29616612/4750575 Is this bad practice? It seems like totally incorrect usage of a ref -- there's only 1 identity at play, not multiple

ccann21:07:34

(defn dequeue!
  "Given a ref of PersistentQueue, pop the queue and change the queue"
  [queue-ref]
  (dosync
    (let [val (peek @queue-ref)]
      (alter queue-ref pop)
      val)))

(let [q (ref clojure.lang.PersistentQueue/EMPTY)]
           (dosync (alter q conj 1 2 3)
                   (alter q conj 5))
           (fu/dequeue! q)
           => 1
           (seq @q)
           => (2 3 4 5))

emccue22:07:18

@ccann Yeah, that seems excessive, but its not incorrect since changes to a ref are ordered

emccue22:07:35

an atom would also work

emccue22:07:45

but also so would just a regular java Queue

ccann22:07:04

The code in question actually has an atom containing a map where the values are refs to PersistentQueues.

ccann22:07:31

Every request thread dereferences the atom, finds the queue it wants to update, and then runs dequeue!

noisesmith22:07:58

@emccue that code would be erroneous with an atom, a deref inside a function setting the value is a race condition

emccue22:07:17

^(yeah, you would need to rewrite in terms of swap to use an atom)

ccann22:07:31

I was thinking of modifying the code to just use swap-vals! on an atom containing a map with values of just PersistentQueues

emccue22:07:52

eh, you probably still want a map of name -> stateful queue

ccann22:07:11

but I'm not sure how to compare the performance of every thread swapping the same atom versus every thread dereferencing the same atom and then altering a queue ref

emccue22:07:22

i'd say move all the queue ops to a single namespace as a start

emccue22:07:38

make sure that noone is derefing the queue directly

emccue22:07:57

then you can start to write an impl using an atom/arrayblockingqueue/whatever and do profiling

noisesmith22:07:07

deref is cheap, swap and alter are slightly more expensive, especially if there is contention (multiple threads trying to set the same container)

noisesmith22:07:15

IMHO the only reason to use refs (rather than an atom containing a map with all your values nested in it) is if there is too much contention / retry activity on the atom, refs allow coordination while reducing contention

noisesmith22:07:33

but clojure apps typically don't demand that much of their mutable stores

hiredman22:07:50

I would definitely look at swapping out the usage of PersistentQueue for some java queue (linkedblockingqueue or whatever) if you don't need immutable queues

ccann22:07:13

alright, will do

ccann22:07:27

Not super familiar with the Java queue options and there seem to be quite a few

emccue22:07:09

afaik the java queues are usually made for "multi write, multi read" stuff

hiredman22:07:17

linkedblockingqueue, dequeue! calls the poll method, and enqueue is the put method

hiredman22:07:24

I would even look at replacing the outer atom + map with a concurrenthashmap, if you are never using it as a value

hiredman22:07:50

when you nest mutable references (atoms, refs, agents, etc) like that, you mostly lose what is great about the clojure kind of identity model (mutable identities pointing to immutable values)

ccann22:07:18

yeah absolutely, seeing refs nested in an atom was the "head scratcher" moment that prompted investigation

hiredman22:07:09

at least they recognized the inherent race conditions that can arise when trying to dequeue from an immutable queue in an atom and used a ref

ccann22:07:53

I was very confused at first as I had never seen ref used in the wild, but they had sound reasoning

emccue23:07:32

I made a comment on that stack overflow post with a pretty simple 1-1 with LinkedBlockingQueue

👍 3