What is sort of the "standard" way to deal with concurrent data structures with mutable state in clojure? Examples work wonders. So lets take the standard bank account example:
(defprotocol AccountAPI
(withdraw [this amt] "withdraw the given amount from the account")
(deposit [this amt] "deposit the given amount into the account"))
(defrecord Account [name balance]
AccountAPI
(withdraw [this amt] (conj this {:balance (- (:balance this) amt)}))
(deposit [this amt] (conj this {:balance (+ (:balance this) amt)})))
(def account (atom (->Account "foo" 100)))
(require '[clojure.core.async :as async])
(dotimes [_ 5]
(async/thread
(swap! account withdraw 2)
(swap! account deposit 1)))
@account ;; => {:name "foo", :balance 95}
This works nicely as withdraw
and deposit
are side effect free. How would this "normally" be done if withdraw
and deposit
are not side effect free? Let's say for example they do I/O. Can I still avoid explicit locking somehow?In case of side effects, use locking
I would say
I used a combination of deftype
,`locking` and set!
for my use case. The result just doesn't look and feel very clojure like, but more like some awkward translated java hence the question.
sometimes a man's gotta do what a man's gotta do
you can use refs too, if you have multiple things of state that need to be in sync
like two accounts
but in that case you still need to separate state changes from the io to perform
maybe returning what to perform as a value
Isn't this what dosync
is for (too)?
@clojurians-slack100 From the docstring of dosync
:
> The exprs may be run more than
> once, but any effects on Refs will be atomic.
Good to point out ๐
That's probably why we keep it free from side effects, like in this example, right? https://clojuredocs.org/clojure.core/dosync#example-542692c7c026201cdc32698d (which is really my primary link to that page ๐)
Or is dosync
actually just a bad solution to the problem?
i read a paper once where they implemented a few different ways to coordinate side effects in a transaction within Clojureโs STM
side effects can happen multiple times in the body of dosync, that's the message
so don't use side effects in there, unless the side effects may happen multiple times
https://projekter.aau.dk/projekter/files/213827517/p10DOC.pdf
the one that felt most natural to me was the โdeferโ method that basically only ran the side effect once it was sure the transaction would commit
i think you can actually do something basically like that using agents
Agents already do that: > Agents are integrated with the STM - any dispatches made in a transaction are held until it commits, and are discarded if it is retried or aborted. https://clojure.org/reference/agents
The ants simulation uses this functionality: https://github.com/juliangamble/clojure-ants-simulation/blob/master/src/ants.clj
I have a sequence of maps representing tabular data rows, and i want to assoc in each row the running sum of the value of another "column":
(def rows
[{:price 10 :qty 6}
{:price 11 :qty 7}
{:price 15 :qty 20}
{:price 14 :qty 7}
{:price 13 :qty 8}
{:price 11 :qty 2}])
(defn with-reductions-col [old-key new-key rf rows]
(let [agg (drop 1 (reductions rf (rf) (map old-key rows)))]
(map (fn [row v] (assoc row new-key v)) rows agg)))
(with-reductions-col :qty :total + rows)
;;=>
({:price 10, :qty 6, :total 6}
{:price 11, :qty 7, :total 13}
{:price 15, :qty 20, :total 33}
{:price 14, :qty 7, :total 40}
{:price 13, :qty 8, :total 48}
{:price 11, :qty 2, :total 50})
Are there any libraries that help do this kind of thing efficiently, so I could append multiple rows in a single pass?Actually I just realized my old favorite https://github.com/cgrand/xforms probably does what i need with transjuxt + reductions
Perhaps incanter
can do it. and according to https://slabruyere.net/assets/pdf/2019-04-24-clojure-for-data-science.pdf other statistics lib may be able to do it.
i'm really just curious to learn more... is there documentation that would explain why i consistently get sets seq'd in the same order? for example:
user=> (seq #{1 2 3})
(1 3 2)
user=> (seq #{"1" "2" "3"})
("3" "1" "2")
i'm surprised to get that same (non-sequential) order on 2 different machines, even clj and cljs (which i was even more surprised to find)... i'm bewildered that the order appears reliable (even though i would never want to rely on it).
if anyone happens to have insight i'd love to know.I haven't checked it but I imagine it's due to how the hash function is implemented for those particular types (`long` and str
).
i suppose it must be, i just never expected "unordered but consistent"
user=> #{1 2 3}
#{1 3 2}
user=> #{1 2 3 4}
#{1 4 3 2}
user=> #{1 2 3 4 5}
#{1 4 3 2 5}
user=> #{1 2 3 4 5 6}
#{1 4 6 3 2 5}
user=> #{1 2 3 4 5 6 7}
#{7 1 4 6 3 2 5}
user=> #{1 2 3 4 5 6}
#{1 4 6 3 2 5}
user=> #{1 2 3 4 5}
#{1 4 3 2 5}
user=> #{1 2 3 4}
#{1 4 3 2}
user=> #{1 2 3}
#{1 3 2}
@clj149 try making sets of this
(defn o [] (let [r (Math/random)] (reify Object (hashCode [_] r)))
it is based on hash function (which is not guaranteed between versions/jvms/etc), but is stable if those variables are the same
thanks, that's what i was hoping to hear
i think java's hashset started purposefully shuffling the items in order to break people who relied on the order
yeah, i have a perl background so i'm used to that
I think there may be some cases where insertion/removal order around the internal tree order may result in different orders, but you'd need a bigger set and a series of "modifications"
I think there may be some cases where insertion/removal order around the internal tree order may result in different orders, but you'd need a bigger set and a series of "modifications"
๐