clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
FiVo 2021-06-28T16:24:30.087400Z

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?

borkdude 2021-06-28T16:28:05.087700Z

In case of side effects, use locking I would say

FiVo 2021-06-28T16:37:49.090400Z

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.

borkdude 2021-06-28T16:38:46.090700Z

sometimes a man's gotta do what a man's gotta do

๐Ÿ‘ 2
emccue 2021-06-28T17:00:09.092400Z

you can use refs too, if you have multiple things of state that need to be in sync

emccue 2021-06-28T17:00:21.092700Z

like two accounts

emccue 2021-06-28T17:00:57.093100Z

but in that case you still need to separate state changes from the io to perform

emccue 2021-06-28T17:01:12.093400Z

maybe returning what to perform as a value

walterl 2021-06-28T17:04:09.093900Z

Isn't this what dosync is for (too)?

โœ”๏ธ 1
borkdude 2021-06-28T17:43:07.094900Z

@clojurians-slack100 From the docstring of dosync: > The exprs may be run more than > once, but any effects on Refs will be atomic.

1
walterl 2021-06-28T17:56:53.096Z

Good to point out ๐Ÿ‘

walterl 2021-06-28T17:57:41.096200Z

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 ๐Ÿ˜›)

walterl 2021-06-28T17:57:54.096400Z

Or is dosync actually just a bad solution to the problem?

lilactown 2021-06-28T18:00:50.097400Z

i read a paper once where they implemented a few different ways to coordinate side effects in a transaction within Clojureโ€™s STM

borkdude 2021-06-28T18:01:39.097600Z

side effects can happen multiple times in the body of dosync, that's the message

borkdude 2021-06-28T18:02:05.097900Z

so don't use side effects in there, unless the side effects may happen multiple times

๐Ÿ‘ 1
lilactown 2021-06-28T18:04:04.099600Z

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

lilactown 2021-06-28T18:04:59.100100Z

i think you can actually do something basically like that using agents

phronmophobic 2021-06-28T18:07:42.100500Z

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

phronmophobic 2021-06-28T18:09:05.101200Z

The ants simulation uses this functionality: https://github.com/juliangamble/clojure-ants-simulation/blob/master/src/ants.clj

2021-06-28T20:39:30.105Z

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?

2021-06-28T20:44:29.105200Z

Actually I just realized my old favorite https://github.com/cgrand/xforms probably does what i need with transjuxt + reductions

Apple 2021-06-28T21:42:02.105600Z

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.

rwstauner 2021-06-28T21:52:53.109900Z

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.

p-himik 2021-06-28T21:54:48.110Z

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).

rwstauner 2021-06-28T22:01:21.110300Z

i suppose it must be, i just never expected "unordered but consistent"

rwstauner 2021-06-28T22:01:43.110500Z

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}

emccue 2021-06-28T22:02:28.110700Z

@clj149 try making sets of this

emccue 2021-06-28T22:03:04.110900Z

(defn o [] (let [r (Math/random)] (reify Object (hashCode [_] r)))

alexmiller 2021-06-28T22:03:15.111100Z

it is based on hash function (which is not guaranteed between versions/jvms/etc), but is stable if those variables are the same

rwstauner 2021-06-28T22:03:52.111300Z

thanks, that's what i was hoping to hear

emccue 2021-06-28T22:03:54.111500Z

i think java's hashset started purposefully shuffling the items in order to break people who relied on the order

rwstauner 2021-06-28T22:06:04.111700Z

yeah, i have a perl background so i'm used to that

alexmiller 2021-06-28T22:08:41.111900Z

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"

alexmiller 2021-06-28T22:08:41.112100Z

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"

rwstauner 2021-06-28T22:14:38.112300Z

๐Ÿ™‡