Fork me on GitHub
Jakub Holý (HolyJak)07:04:52

For a project I have written a stateful map and stateful filter using transducers. Perhaps it can be useful to others as well?

Ben Sless09:04:39

Can it be generalized to a reducing function?

Ben Sless10:04:06

With something like

(defn with-state
  [txf trf f init]
  (let [state (volatile! init)
        tf (txf trf)]
    (fn [in]
      (let [old @state]
        (vswap! state tf in)
        (f old in)))))
You can generalize. the t stands for transition

Ben Sless10:04:34

With some more hand waving:

(defn with-state
  ([trf f]
   (with-state trf f nil))
  ([trf f init]
   (with-state identity trf f init))
  ([txf trf f init]
   (let [state (volatile! init)
         tf (txf trf)]
     (fn [in]
       (let [old @state]
         (vswap! state tf in)
         (f old in))))))

(defn right [x y] y)
(sequence (stateful-filter not= right) [1 2 2 2 3 4 4 5])
(sequence (filter (with-state right not=)) [1 2 2 2 3 4 4 5])

Jakub Holý (HolyJak)10:04:45

Great comments, Ben, thanks a lot!

Ben Sless11:04:36

If you don't want to use a volatile for performance reasons, since it won't be thread safe anyway, you can use a box type. Probably overkill 🙂

(definterface IBox
  (_put [x])
  (_look []))

(deftype Box [^:unsynchronized-mutable v]
  (_put [this x] (set! v x))
  (_look [this] v))

(defn with-state2
  ([trf f]
   (with-state2 trf f nil))
  ([trf f init]
   (with-state2 identity trf f init))
  ([txf trf f init]
   (let [^Box state (->Box init)
         tf (txf trf)]
     (fn [in]
       (let [old (._look state)]
         (._put state (tf old in))
         (f old in))))))

❤️ 3
Ben Sless11:04:55

I used an interface because I couldn't get rid of reflection warnings otherwise

Ben Sless11:04:17

Probably didn't try hard enough

Jakub Holý (HolyJak)13:04:32

not applicable, since we need something mutable

Ben Sless19:04:05

Java, mutable by default

(.val ^Box b)
user=> (set! (. ^Box b val) 2)

facepalm 3
Ben Sless19:04:22

I did not know we had this object lying around 🙂

Timofey Sitnikov12:04:25

Good morning Clojurians, I read the article this morning and wanted to know if it makes sense for a small Clojure website with the DB on the the same machine, with the goal to minimize the complexity. Also, if SQLite is used, does it make sense to use HekaryCP?


Good morning! I've been building some automation at work with Clojure/SQLite recently, and it's been great. Would be fantastic for a small website. Regarding connection pooling, it might make sense to use it. The JDBC driver for SQLite will serialize when using the same connection across multiple threads:


There are also pure Java alternatives like H2, Derby and HSQLDB

👍 3

TBH I have only used SQLite though. It has been pretty good although the column types are kind of crappy compared to e.g. Postgres.

Timofey Sitnikov13:04:44

I do wonder if the SQLite is quicker than Postgres when both run on the same machine.


That'll depend on the workload. For something read-heavy like a website, SQLite will probably be as fast or faster than PostgreSQL. For anything write-heavy, PostgreSQL will perform better.


"small website" is really ambiguous wording tbh. apps can be small but also represent a lot of work and hopes (e.g. your MVP startup) any startup that intends to satisfy actual customers should have make a minimally reasonable HA setup. That practically rules out sqlite and friends I don't want to come off as dogmatic but honestly "cattle not pets" is pretty hard to argue against in 2021. You can get a HA psql for 100 bucks or whatever on aws/heroku/...


Shameless plug: if you are not wedded to SQL, you may consider Datalevin, a SQLite like Datalog database for Clojure, it will be much simpler to use in Clojure, no drivers to worry about and native EDN input/output


Ya, I feel for a small use case like you describe, it will be even better to go with a Java native solution like H2 or Datalevin. Then you don't even need to install anything else, its all embedded in your app.

Timofey Sitnikov10:04:14

@U45T93RA6, Yes, this is an interesting comment, I do feel the same way. That article made me waver but every developers dream is for their website to go viral. If that happens, and multiple machines need to be spun up, scaling postgres is quick, so it is probably a better choice.

Noah Bogart18:04:41

is this an idiomatic way to schedule a function to be called every second or so? I need it in two places and don't want a whole dependency (like chime) to handle it.

(defn tick
  "Call f every ms. First call will be after ms"
  [active? callback ms]
    (while @active?
      (Thread/sleep ms)


Maybe you need some error handling in case callback fails?

👍 3

you might want something more robust than Thread/sleep alone if the timing precision matters, but it probably doesn't

Noah Bogart19:04:57

it does not. This is to clean up empty lobbies in a game sever


also, I like to use a delay or promise for turn-off-switch args, because unlike an atom they are simple, limited purpose, and one way


I like to use the weak / specific construct when possible, it helps clarify intent

Noah Bogart19:04:59

I don't know much about those objects. do you have an example of how I would use it here?


example coming up

👍 3


(ins)user=> (defn tick [done? callback ms] (future (while (not (realized? done?)) (callback) (Thread/sleep ms))))
(cmd)user=> (def signal (delay :done))
(cmd)user=> (tick signal #(println "still going") 10000)
#object[clojure.core$future_call$reify__8454 0x1dab9dd6 {:status :pending, :val nil}]
still going
user=> still going
(force signal)
also I changed the order of callback and Thread/sleep because calling callback again after cancelled seems odd

Noah Bogart20:04:28

Thank you! I’ll read this over


also the code becomes clearer if you have a helper (def pending? (complement realized?))

Leonid Korogodski20:04:52

The oddest thing. I have two projects. Both use malli 0.2.1 (according to lein deps :tree). But one of them accepts the syntax [:map [:a string?] [:b string?]] just fine, while the other throws an error on (restart):

data-spec collection [] should be homogeneous, 3 values found
Any idea what could be the cause?


If that error might be caused by a difference in malli versions, then perhaps you have an uberjar on your classpath that contains a different version of malli.


@lkorogodski Perhaps a question for #malli