Fork me on GitHub
#clojure
<
2022-09-28
>
grav08:09:54

Visualization libs: I see quite a few wrappers around Vega/Vega-Lite, like Oz and Hanami. Any experiences with these, or maybe even something simpler?

craftybones08:09:00

It depends on what you want

craftybones08:09:46

Oz and Hanami are a little more than just wrappers. They do a few more things. If you are looking for something that's even lighter, then consider https://github.com/applied-science/darkstar

craftybones08:09:52

If I remember right, Oz actually uses it

craftybones08:09:47

I've used Oz more than Hanami. Simple and elegant.

craftybones08:09:28

Babashka with Vega/Lite is another option

grav08:09:00

Ah, nice. Acually might fit the bill. It's for a coding challenge, so bb might be even better suited

grav08:09:06

Thanks for the info @U8VE0UBBR

d-t-w13:09:50

We use Apache Echarts, it is excellent: https://echarts.apache.org/en/index.html

d-t-w13:09:28

We have single namespace wrapper that we'll oss as a mini project in the next couple of weeks. Doesn't do much other than make echarts a bit simpler to use in cljs.

❤️ 1
russmatney19:09:53

depending on the use-case, https://github.com/nextjournal/clerk might be a good fit too - it lets you mix jvm/js clojure code, and supports a few visualization libs (plotly, vega lite) out of the box - the https://github.clerk.garden/nextjournal/book-of-clerk/commit/d74362039690a4505f15a61112cab7da0615e2b8/ has good coverage and is itself a clojure file rendered by clerk (https://github.com/nextjournal/clerk/blob/main/book.clj)

💡 1
pesterhazy10:09:12

What's a good way to create a graph for namespace dependencies? I'm using deps, not lein Desiderata: • simple • flexible - e.g. ability to exclude partial hierarchies

borkdude10:09:34

clj-kondo is able to output the dependencies between namespaces: https://github.com/clj-kondo/clj-kondo/blob/master/analysis/README.md Some tools built on this are vizns and morpheus (as seen here: https://github.com/clojure/tools.deps.alpha/wiki/Tools#deps-management) You could probably also use tools.namespace for this.

vlaaad12:09:06

Oh sorry, you are talking about ns deps, my bad

pesterhazy12:09:00

@U47G49KHQ yes, tools.deps.graph looks useful, but it's only for (external) maven deps - I'm looking for (internal) ns deps

pesterhazy12:09:18

@U04V15CAJ whoa that was easy!

🚀 1
pesterhazy12:09:00

All I needed was

clojure -M:clj-kondo-tools -m clj-kondo.tools.namespace-graph src

👍 1
pesterhazy12:09:10

I'm so glad I asked here 🙂

bpiel16:09:52

How many go-blocks is too many?

bpiel17:09:32

What about millions?

Joshua Suskalo17:09:19

part of the point of go block concurrency is that thread count is not a directly limiting factor in concurrency. At millions you may see memory limitations as you store continuations or perhaps latency issues as the threads those millions of execution threads have to map to swap between them. If most of those threads are parked most of the time you may not see too much performance degradation, you just have to be careful with memory.

emccue20:09:08

@U4FFD43T4 But also... don't bother if you can use Java 19

bpiel20:09:41

Because virtual threads?

bpiel20:09:25

I haven't seen much written on how they compare. Are virtual threads hands down better?

Joshua Suskalo20:09:26

Notable is that virtual threads have identical restrictions/limitations as I said above for go threads. Virtual threads are basically less-limited go threads.

emccue20:09:12

> Are virtual threads hands down better? Pretty much. go blocks do what virtual threads do just in "user space" - so your stack traces will start to suck

emccue20:09:23

and you need to take special care around stuff that blocks

Joshua Suskalo20:09:40

Virtual threads are hands-down better because of the type of concurrency they provide, the ability of the JVM to optimize them, and the fact that all IO that uses standard java features doesn't block the OS thread, and that stacktraces and debuggers can handle them normally.

emccue20:09:55

idk if core.async's chan needs another look or if there is a better potential implementation now

emccue20:09:03

but (one sec)

Joshua Suskalo20:09:36

Quite possibly, but it does use standard JVM locking and monitor stuff, which should work fine with virtual threads and not pin carrier threads.

bpiel20:09:06

This is all great to know. I'll play with virtual threads. Thanks

emccue20:09:27

(require '[clojure.core.async :refer [chan >!! <!!]])

emccue20:09:32

(defmacro scram 
  [& body]
  `(let [chan# (chan)]
     (Thread/startVirtualThread 
       (fn []
         ~body))
     chan#))

Joshua Suskalo20:09:33

Oh, I need to make it public

emccue20:09:36

err, one set

Joshua Suskalo20:09:45

It's not battle-tested yet, but this is a monkeypatch that you can require before you require any libraries that use core.async and it should make them use virtual threads by default.

Joshua Suskalo20:09:59

the link should work now

emccue20:09:21

(defmacro scram 
  [& body]
  `(let [chan# (chan)]
     (Thread/startVirtualThread 
       (fn []
         (>!! chan# (do ~@body))))
     chan#))
=> #'user/scram
(scram 3)
=>
#object[clojure.core.async.impl.channels.ManyToManyChannel
        0x7dbe3ce6
        "clojure.core.async.impl.channels.ManyToManyChannel@7dbe3ce6"]
(<!! *1)
=> 3

emccue20:09:23

here you go

emccue20:09:34

you should be able to replace every "go" with "scram"

👍 1
bpiel20:09:36

Also TIL sourcehut

Joshua Suskalo20:09:00

Also https://git.sr.ht/~srasu/plet this is also not battle-tested but is about the structured concurrency incubator

emccue20:09:21

hmm looking at what joshua wrote maybe also want this

Joshua Suskalo20:09:21

sourcehut is really nice. I really like the email-based workflow because it allows you to manipulate history of proposed changes right up till they get merged.

emccue20:09:08

nvm binding conveyor fn isn't public

Joshua Suskalo20:09:19

plet here is for examples like:

(plet {val-a (alts [(http/get mirror-1) (http/get mirror-2)])
       val-b (http/get second-thing)}
  (do-stuff val-a val-b))

👍 1
emccue20:09:11

bit of twiddling to do to get dynamic vars over ( see this if your needs are simple enough that future works https://clojurians.slack.com/archives/C03S1KBA2/p1664289931495499)

emccue20:09:37

(defmacro scram 
  [& body]
  `(let [chan# (chan)]
     (future (>!! chan# (do ~@body)))
     chan#))
=> #'user/scram

emccue20:09:48

use this + the monkey patch in the link

Joshua Suskalo20:09:52

You also need to close the chan

emccue20:09:14

I ...knew? that

Joshua Suskalo20:09:17

otherwise this is a breaking api change compared to go

emccue20:09:41

what he said^

Joshua Suskalo20:09:06

but yeah, your scram is great except that you also need to patch parking put and take to be blocking instead otherwise regex-replace won't work. This is why I did the spindle.monkey-patch ns though, it just does all that for you.

Joshua Suskalo20:09:39

only restriction is you have to require the patch before you require anything else that might use core.async

emccue20:09:40

(require '[clojure.core.async :as async])

(defmacro scram 
  [& body]
  `(let [chan# (async/chan)]
     (future (async/>!! chan# (do ~@body))
             (async/close! chan#))
     chan#))
like this? what is go's behavior on exceptions again?

emccue20:09:13

@U4FFD43T4 You see what we mean by better maybe? What used to take a whole complex DSL is now just a feature of the vm

👍 1
emccue20:09:41

core.async really is and was a marvel - just glad to not need marvels for this

Joshua Suskalo20:09:10

go closes even on exceptions, you need to do a try/finally

emccue20:09:10

(defmacro scram 
  [& body]
  `(let [chan# (async/chan)]
     (future (try
               (async/>!! chan# (do ~@body))
               (finally
                 (async/close! chan#))))
     chan#))

Joshua Suskalo20:09:16

yep, that looks good to me

Ben Sless19:09:32

Missing a 3?

borkdude19:09:15

Too many go blocks might lead to a 137 exit code ;)

nice 1
zalky20:09:13

Hey all, what's the best way to get the symbol from a var? The clojure docs for https://clojuredocs.org/clojure.core/symbol say that (symbol #'x) should work: >

Returns a Symbol with the given namespace and name. Arity-1 works
> on strings, keywords, and vars.
But this throws an exception.

borkdude20:09:36

Probably need to upgrade:

Clojure 1.11.0
user=> (symbol #'inc)
clojure.core/inc
What is your clojure version?

👍 1
zalky20:09:59

Ah, right on. 1.9

sreekanthb20:09:53

user=> `inc
clojure.core/inc
user=> `x
user/x
https://clojure.org/reference/reader#syntax-quote
can be used resolve to Symbol.
`symbol`  throws exception if the var is not defined, 
doesn’t throw exception and resolves to current context.

ahungry20:09:25

Any jdbc/sqlite3 experts out here? I'm trying to increase my mass insert throughput - in nodejs this was easy with a couple db.run commands to set PRAGMA synchronous = OFF, in tandem with PRAGMA journal_mode = MEMORY. However, even when calling these in a with-db-connection macro, it seems to have no effect. (https://clojure.github.io/java.jdbc/#clojure.java.jdbc/with-db-connection and https://github.com/xerial/sqlite-jdbc are the related libs). I've been digging through the sqlite-jdbc source, but not getting any good matches on how one may define this.

ahungry20:09:53

Rather consistently, I'm getting about 1000 inserts a second. On the nodejs implementation, it's easily 10x this

seancorfield20:09:58

Are you using insert! or insert-multi! or some other java.jdbc call?

seancorfield20:09:46

For batch insert to work "as expected", many JDBC drivers expect you to provide additional hints or options on the connection URL (i.e., in the db-spec hash map or whatever). I know of options for PostgreSQL and MySQL (they're different, of course) but I'm not familiar enough with SQLite to offer specifics.

seancorfield20:09:11

The next.jdbc docs have some details about this (for some DBs). java.jdbc doesn't have great docs.

seancorfield20:09:31

(any reason you're still using java.jdbc instead of switching to next.jdbc?)

ahungry20:09:18

Thanks! No reason other than just referencing a prior project of mine that used java.jdbc - I'll give next a try 🙂 - I was using insert-multi!, now I'm trying insert! in a transaction with a pmap

seancorfield20:09:39

insert! is always going to be slower than a batch insert -- because it makes a roundtrip to the DB for each row. Batch inserts can send a whole block of rows to the database in a single roundtrip so they should be faster. Although you may need some additional connection options to tell the driver to actually do that properly.

seancorfield20:09:59

pmap is almost never the right solution to any problem BTW.

💯 1
ahungry20:09:40

Oh, why is that? That was one of the things I liked - converting a sequential map into a pmap always felt like "free concurrency". I'm doing some cpu intensive binary file parsing (10k files) and after parsing, mapping them to an sqlite db - I'm fairly certain my sqlite still isn't doing the pragma, but some pmaps have changed the overall time from 30 seconds down to 15s - I've benched the parsing at 5s, so if sqlite would honor my pragma, I think the whole thing would be under 10s now

ahungry20:09:36

(in this case, I guess I should note, I need parallelization, not just concurrency)

seancorfield20:09:45

pmap is unmanaged concurrency. Sometimes the overhead it introduces isn't worth it, sometimes you can end up with a lot of contention and/or overwhelming your system. You can't tune it so it's a bit of a sledgehammer. That said, we do use it in a couple of places at work -- just six places in 142K lines of Clojure -- but those are legacy code that we just haven't gotten around to rewriting yet on top of either executors or core.async 🙂

ahungry21:09:30

haha that makes sense, ty for the insight 🙂

Lone Ranger01:09:58

@U04V70XH6, are these the executors you speak of? https://blog.raek.se/2011/01/24/executors-in-clojure/ :thinking_face:

seancorfield01:09:53

Java Executors, yes.

seancorfield01:09:31

You have a lot more control over threads, pools, queues etc. It also sets you up nicely to leverage virtual threads in Java 19.

Craig McDaniel00:10:43

FYI, threads and SQLite sometimes don't get along very well. Had some negative experience with the Xerial driver some years back. Just a warning. There be dragons.

Jérémie Salvucci20:09:56

Hi, I was expecting the following pattern (relying on clojure.core.match/match) as something straightforward but this is not the case. How do you use pattern matching with correct syntax?

(defn my-assoc-in [m ks v]
  (match ks
    [] m
    [k] (assoc m k v)
    [k & ks] this pattern fails for vectors with at least 2 elements))

pppaul21:09:01

(let [form [1 2]]
  (match form
         [] :a
         [k] :b
         [k & ks] :c))
works for me

pppaul21:09:36

can you show a complete test of the failure you are experiencing? (similar to my example)

Jérémie Salvucci21:09:18

If I use this function

(defn my-assoc-in [m ks v]
  (match ks
    [] m
    [k] (assoc m k v)
    [k & ks] 3
    :else 4))

(my-assoc-in {:a {:b 2}} [:a :b] 3) returns 4 instead of 3

pppaul21:09:14

when you are binding symbols, use something like ?name syntax

pppaul21:09:24

cus match cares about external symbols too

pppaul21:09:06

(defn my-assoc-in [m ks v]
  (match ks
         [?k & ?ks] 3
         ))

pppaul21:09:24

that's what i do, at least

Jérémie Salvucci21:09:43

oh you mean, if I use an external symbol it will apply equality?

pppaul21:09:21

match can use external symbols to match against, which is cool, but you ran into the main issue with that feature.

pppaul21:09:06

match was using ks to match against, so [k & ks] is impossible to match in that case

pppaul21:09:24

(and (vector? ks) (>= (count ks) 1)) (try
                                           (let 
                                             [ks_left__121914
                                              (subvec ks 0 1)]
                                             (cond
                                               (and
                                                 (vector?
                                                   ks_left__121914)
                                                 (==
                                                   (count
                                                     ks_left__121914)
                                                   1))
                                               (let 
                                                 [?k
                                                  (nth
                                                    ks_left__121914
                                                    0)
                                                  ks (subvec ks 1)]
                                                 3)
                                               :else
                                               (throw
                                                 clojure.core.match/backtrack)))
                                           (catch
                                             Exception
                                             e__5150__auto__
                                             (if
                                               (identical?
                                                 e__5150__auto__
                                                 clojure.core.match/backtrack)
                                               (do
                                                 (throw
                                                   clojure.core.match/backtrack))
                                               (throw
                                                 e__5150__auto__))))
that is the macro expand. ks is being used directly in a lot of places.

pppaul21:09:56

so, using your test case, the match expands to something like [k & [:a :b]]

Jérémie Salvucci21:09:35

definitely, I ignored the use of external symbols. I was expecting symbols to be only pattern specific (as in most ML based languages). Good to know, you saved my evening, thank you!

pppaul21:09:59

(defn my-assoc-in [m ks v]
  (let [form [1 ks]]
    (match form
           [?k ks] 3
           ))
  )

(my-assoc-in
  {:a {:b 2}}
  [:a :b]
  3)
can't use the &, but the match works.

pppaul21:09:12

i think meander actually doesn't do crazy things like match, but meander is also a lot more verbose in most cases. the macro expands for match are not too hard to read, or step threw, though. macro expanding is a good tool for figuring out what they are doing

Jérémie Salvucci21:09:12

yes, now I know about this equality, the error message is clear!

p-himik22:09:18

Never mind, I mixed my REPLs... Inspired by https://clojurians.slack.com/archives/C03S1L9DN/p1664395023158019 Why does a in the metadata get evaluated? But if I did (def ^a x []), it wouldn't be.

user=> (let [a 7] (meta ^a []))
{:tag 7}
user=> (meta ^7 [])
Syntax error reading source at (REPL:8:9).
Metadata must be Symbol,Keyword,String or Map