Fork me on GitHub
#clojure
<
2020-05-22
>
mpenet02:05:55

You could do that with swap-vals! too (to do the check on successful swaps)

hiredman02:05:48

you'd end up queueing actions more than once in the event of a retry

mpenet02:05:24

You'd compare the ret values of swap-vals, not inside f

mpenet02:05:05

(when (reduce = (swap-vals a f)) (do-stuff))

andy.fingerhut02:05:09

I thought swap! and swap-vals! only ever return after a single successful swap? f can be retried any number of times, but I do not understand why (do (swap-vals a f) (do-stuff)) would not work.

andy.fingerhut02:05:32

Assuming f has no side effects, having put them all into do-stuff

mpenet02:05:52

I think they want to know if the atom value changes

andy.fingerhut02:05:43

I read their initial question as that they have about 10 different f functions, and some of them have side effects

mpenet02:05:45

swap-vals only allows to check that via its ret

mpenet02:05:11

hiredman s code does basically that

andy.fingerhut02:05:21

Sure, if they can tease apart their side-effecting f functions into a side-effect free part f, and a separate side-effecting part do-stuff, then the existing swap! should suit their needs. It seems they are looking for any advice that allows them not to tease them apart like that.

mpenet02:05:44

swap-vals + the queue polling. As i said before my example just covers a simpler cas

mpenet02:05:13

(Instead of doing deref, run f, cas)

andy.fingerhut02:05:27

I may have missed the context, if you were suggesting changes to hiredman's code snippet above, rather than something to use without his my-swap! or something similar.

mpenet02:05:00

On mobile, I hate typing on these, I was maybe not clear enough

mpenet02:05:46

But yes i was suggesting a simplification of his code, not replacement

andy.fingerhut02:05:50

Oh, you can be as clear as can be, and readers like me can skip reading a couple of messages of context that critically matter, and get confused. That's on me 🙂

mpenet02:05:38

I think he misread that too, so it's at least partially on me too

hiredman03:05:14

I did not, you cannot use swap-vals and have it work correctly with that code

hiredman03:05:22

Swap-vals can rerun f multiple times, and you will never know how many times it ran it before it successfully completes

hiredman03:05:39

So you cannot use a function which both changes a value and (as a side effect) queues an action to be run

hiredman03:05:27

Because if f is run multiple times you well get your actions queued multiple times

hiredman03:05:24

Even if you assume only one action is queued, you still can't just take the first

mpenet03:05:26

It's not what i suggested

mpenet03:05:25

It would replace 3 lines in your code. I am not saying it should run side effect

hiredman03:05:50

Swap-vals returning two equal values doesn't tell you anything

hiredman03:05:54

Other than the function you passed it is identity, it doesn't mean the case failed

hiredman03:05:29

Compare-and-set is strictly more powerful than swap! And swap-vals because using CAS failures are visible, while with higher level functions like swap! And swap-vals! You only see the success, which means if you have actions you need to do when the cas fails (like clear a queue) you cannot use swap and friends

mpenet04:05:59

I know what cas does but I see what you mean, I should actually have read the OPs intent before your code

didibus04:05:58

The conclusion is that @mpenet is correct that one can compare the result of swap-vals! to know if the value has changed after a successful swap and then run a side effect based on it. But @hiredman is also correct if we assume OP needed to take some actions when the swap fails. Is that a good reading of the situation?

didibus04:05:16

And @andy.fingerhut is also correct that one can just wait for swap! to finish and immediately run a side effect after if we neither care about the swap failing or the value having changed

hiredman05:05:22

What you lose of you do this is the ability to have f change the value of the atom, and also take action based on the changes it made

hiredman05:05:57

For example if the atom is holding a state in some state machine you cannot both step the state machine and run some action based on that step

hiredman05:05:05

If you deref you have a race condition, if you use swap-vals you basically have to step twice (once when actually swapping and one again with the old and new value to figure out what the step was and run your action)

didibus06:05:32

Right okay. I get it now. I just wanted to confirm the simpler assessments in case somehow I was missing something about the whole atom machinery

seancorfield04:05:51

@didibus If f did not change the value, you can't tell the difference between a successful swap and and failed one tho', right?

seancorfield04:05:37

(although if f is truly a pure function, it could either be identity or else it's at a fixed point)

didibus05:05:39

Would swap return after x number of failed attempts?

didibus05:05:46

I thought it block forever

mpenet05:05:45

If you don't want the swap to actually happen (ex can't have it be identity in "failure" case) you need cas, otherwise swap-vals is fine.

mpenet05:05:01

deref + cas can be racey tho. I can imagine a scenario where you can have change happen in another thread (if not strictly using my-swap) and you could miss changes happening between the 2, think in dec inc dec type of scenario

mpenet05:05:43

But that's pushing it. I guess if you use that kind of code you would avoid doing that

hiredman05:05:05

Like, I am shocked at having to explain this, it seems super clear by analogy to the way agents and the stm hold agent sends until the current action or transaction completes

hiredman05:05:46

The only place I have ever seen the stm used in production code was because author wanted an atom, but also needed side effects in the swapped function, so they used a ref and an agent

hiredman05:05:54

The ref could give them a cas, and the stm holds agent sends until the tx completes (and only sends the sends from the successfully tx)

hiredman05:05:44

Same thing, but no stm. And the particular case in our billing code I am thinking of the "atom" is actually an IAtom impl backed by a row in a database so the stm wouldn't solve anything

orestis06:05:48

That sounds interesting and strange. Would love to hear some details behind the decision to not make an explicit protocol about it.

didibus06:05:27

So we're talking of synchronizing the side effect and the swap? Like either the swap succeeds and the side effect succeed, or none of them are performed?

andy.fingerhut06:05:30

Barring the possibility of unlimited retries because of multiple threads frequently updating an atom, a swap always succeeds, eventually.

andy.fingerhut06:05:19

I guess, also barring other weird things like the function given to swap going into an infinite loop. Not sure about what hapepns if it throws an exception. I'm assuming in my previous statement neither of those things happens.

andy.fingerhut06:05:28

And my understanding is that hiredman's code snippet fairly far above now is a description of one way to get the behavior of my-swap! finishes, and whatever side effect the passed-in function wants to achieve, is only ever done exactly once, after my-swap! finishes. That technique appears to require you to write the updating function f in a particular way, i.e. my-swap! cannot take an arbitrary function with arbitrary side effects and make things work as desired.

didibus06:05:17

I'm thinking, I'd probably just lock if I needed to do side effect + update together atomically.

didibus06:05:04

Or maybe I'd queue up the side effects if it's okay for them to lag behind

Balaji Sivaramgari07:05:29

Hello All, need your help to setup the code scan for Blackduck + Clojure?

Balaji Sivaramgari07:05:03

Any suggestions how to integrate Blackduck with Clojure?

Vincent Cantin07:05:39

If we wanted to celebrate a "Clojure Day" once a year, which day would it be?

Jakob Durstberger07:05:00

When is Rich Hickey’s Birthday? 😄

Jakob Durstberger07:05:10

Or maybe 16th October, because that’s the day of the first release

Vincent Cantin08:05:52

@U04V70XH6 any suggestion ? Do you think it would be possible to have Cognitec make an "official" day?

seancorfield15:05:47

A question better directed at @U064X3EF3 surely?

alexmiller15:05:23

I'm not sure Rich would be excited about publicizing his birthday :)

alexmiller15:05:57

we consider Oct 16 to be Clojure's birthday

alexmiller15:05:24

so that would be my vote

👍 16
🎂 8
seancorfield16:05:59

(it's in my calendar! 🙂 )

👍 16
Setzer2208:05:05

I'm getting a NoClassDefFoundError when calling a static method in a class java class imported from a library (this is the class' source, just in case: https://github.com/camunda/camunda-bpm-platform/blob/master/model-api/bpmn-model/src/main/java/org/camunda/bpm/model/bpmn/Bpmn.java). The error is:

1. Unhandled java.lang.NoClassDefFoundError
   Could not initialize class org.camunda.bpm.model.bpmn.Bpmn
But of course that class is defined. What I think NoClassDefFoundError means in that case is this class' (Bpmn) initializer is itself trying to access a class that's not in my classpath, due to a missing dependency. The problem is, I don't know how to figure out what's actually the missing class. Is there a way I can access the name of the class that can't be found?

Setzer2209:05:37

Ok, so it seems I have conflicting dependencies between packages in my project and this library (yay, JVM! :face_with_rolling_eyes:), so the missing class is probably due to a version clash

Setzer2209:05:57

Any way I can list conflicting versions of packages with tools.deps?

valerauko10:05:55

when an exception goes uncaught, what prints it out and how can i override that?

Setzer2210:05:30

@vale it depends, If you're using CIDER what you're probably seeing is the output of the stacktrace middleware (https://github.com/clojure-emacs/cider-nrepl/blob/master/src/cider/nrepl/middleware/stacktrace.clj). But I guess this relies on actually catching the exception somewhere and inspecting the object.

Setzer2210:05:20

If you mean the actual default stacktrace text you get on uncaught exceptions, I think that's on the JVM, or the implementations of the Exception class (i.e. printStackTrace?) I'm not really sure about that though

valerauko10:05:37

i was wondering what it'd take to format errors better. i can do it in repl with :caught but what about normal execution?

Black11:05:10

Is there any way I can change my local address when making requests throw clj-http?

valerauko03:05:29

what do you mean "change your local address"?

zane19:05:39

If I wanted to implement a new associative collection that does some extra work whenever values are assoced and dissoced would the right thing be to implement IPersistentMap?

alexmiller19:05:34

depends on how you want to use it

alexmiller19:05:05

if you need dissoc, then yes, probably IPersistentMap is the right target (if only assoc, then Associative)

alexmiller19:05:35

while there are good reasons you might want to do this, I think I'd question whether it's really what you want, but hard to tell with no context

zane21:05:53

We’re trying to choose the right abstraction to represent a particular kind of model from our domain.

zane21:05:52

The model needs to be able to “incorporate” and “unincorporate” information in the form of Clojure hashmaps.

zane21:05:48

Initially we went with a Clojure protocol with incorporate and unincorporate methods that take a model, a unique identifier, and a map as arguments.

zane21:05:41

But I’m realizing that if we had the model implement IPersistentMap by delegating to incorporate and unincorporate we could potentially get the model abstraction to play well the existing Clojure library (a-la https://insideclojure.org/2016/03/16/collections/).

zane21:05:48

Hope that makes sense, @U064X3EF3. 🙂

zane21:05:51

Are there good heuristics / rules of thumb for when it’s a good idea to implement collections interfaces like IPersistentMap and when it’s better to go with a different approach? :thinking_face:

alexmiller21:05:59

From what you describe, I do not see much motivation to move beyond just using Clojure data

alexmiller21:05:37

I would only do that if you truly have a new data type with different semantics

krzyz21:05:13

Seems to me the only time you would do that is when Clojure’s standard data structures don’t work for you.

zane22:05:58

Well, the motivation is that whenever someone incorporates or unincorporates data you need to make some modifications to other parts of the model.

zane22:05:26

We could package up all those operations (adding the data and making the corresponding changes to the other parts of the model) into a function / protocol method but then we’d have to reinvent the wheel for operations built on top of Clojure’s assoc that we’d get for free if we implemented Clojure’s collection interfaces. At least that’s the idea.

noisesmith22:05:27

while that would be some convenience, I would find the idea of things like into and merge invoking arbitrary domain logic to be surprising

alexmiller22:05:22

Ditto - we have a tool for custom logic on a data structure, that’s a function :)

zane22:05:55

That makes sense. One argument against this that occurred to me is that it’s likely that we’d wind up violating the performance guarantees that the core collections provide for common operations.

valerauko01:05:18

on a related note, what are those performance guarantees for recent clojure versions? the only chart i find is from 2010 and even that doesn't seem to be official

alexmiller01:05:39

We don’t have one place for that right now (there is a doc site issue to improve some of this)

alexmiller02:05:40

But generally conj/assoc/disj/dissoc/get/contains? should be effectively constant (really log base 32). count may be linear but things marked Counted should be sublinear (often constant). In general stuff that takes seqables is expected to be linear.

didibus02:05:17

Have to agree with everyone else, that sounds like you just need a defn

👍 4
didibus02:05:25

And if you're saying there are different kind of models that need a different logic applied, you could use a multi-method for it

mmer20:05:17

Hi I have a list of strings. I want to join certain of those strings based on a predicate, but the rest I just want to remain as they are, is there a idiomatic way of doing this?

didibus01:05:33

This is a job for group-by I believe

didibus02:05:01

(def strs ["a" "a" "b" "a" "c"])
 
(->> strs
 (group-by
  #(boolean
    (#{"a"} %)))
 ((juxt
   #(str/join (get % true))
   #(get % false)))
 flatten)
 
;;=> ("aaa" "b" "c")

noisesmith20:05:23

sounds like a job for reduce to me, with a structured accumulator having a key for potentially joinable items and a key for collected items

noisesmith20:05:51

(reduce f {:strings [] :partial nil} coll)

noisesmith20:05:51

you'd likely need a wrap up function at the end in case you still had something in :partial (sometimes instead you can treat the end of the [] as a "staging area" where you often take things out, combine, and put back in...)

lvh20:05:17

Is there a JVM switching tool that people like? I'm on multiple platforms (Debian, Fedora, macOS) and also install GraalVM in ~/.local, so reliably switching between JDKs is extremely obnoxious, especially when I'm chasing down a bug that only appears to exist within 1 version

aisamu21:05:53

Nix, if you're using openjdk/jre

alexmiller20:05:05

most people seem to use jenv

dpsutton21:05:19

i've been on sdkman

dpsutton21:05:13

and looking at jenv it seems nicer

mmer21:05:47

@noisesmith Thank, I been trying to work through your answer. I get the destructuring, but what must the function do to reduce - I keep getting out of memory errors

noisesmith21:05:04

I'd expect the function to be something like:

(fn [{:keys [string partial]} elt]
  (if (combine? partial elt)
    {:strings strings
     :partial (str partial elt)}
    {:strings (conj strings elt)
      :partial partial}))
but that's really a guess - it depends on your rules for combining

dpsutton21:05:54

(let [sb (StringBuffer.)
      pred #(even? (count %))]
  (update (reduce (fn [acc s]
                    (if (pred s)
                      (update acc :joined #(.append % s))
                      (update acc :coll conj s)))
                  {:coll []
                   :joined sb}
                  ["a" "ab" "abc" "abcd"])
          :joined
          str))

dpsutton21:05:38

If you’re going to repeatedly join strings use a string buffer

zane21:05:53

We’re trying to choose the right abstraction to represent a particular kind of model from our domain.

zane21:05:51

Are there good heuristics / rules of thumb for when it’s a good idea to implement collections interfaces like IPersistentMap and when it’s better to go with a different approach? :thinking_face:

mmer21:05:02

@noisesmith Thanks - worked it out - been bashing my head around with loops etc for days.

noisesmith22:05:52

speaking of, a good criteria is that every usage of loop that consumes a list in order can be replaced by map or reduce (combined with eg. take-while or reduced if you need early exit)

👍 8
valerauko01:05:18

on a related note, what are those performance guarantees for recent clojure versions? the only chart i find is from 2010 and even that doesn't seem to be official