Fork me on GitHub
#clojure
<
2022-11-17
>
elliot01:11:16

(Is there a newfangled REPL people are using these days besides REPL-y or raw nrepl.cmdline? Thanks!) (rebel-readline doesn’t look like it’s nrepl-compatible?)

seancorfield06:11:53

Don't type into your REPL -- eval code from your editor 🙂

🎯 2
👍 1
seancorfield06:11:34

I used a plain Socket REPL for years with Atom/Chlorine and then VS Code/Clover and there was no REPL window to type into at all -- just an output panel. Now I start an nREPL server from the command-line -- again no input -- and connect to it from VS Code/Calva and I never open the REPL window at all. I use Portal for visualizing data (previously I used Reveal, before that Cognitect's REBL).

borkdude08:11:13

I agree that evaluating code from your editor is more convenient but in some cases I still use the console REPL. E.g. I sometimes work on multiple projects on the same day and want to switch to a project to test something quickly, I don't want another REPL windows in my editor since having many of those tends to get confusing. In such cases I start a plain old console REPL and use require + :reload while making changes in my editor. This even works with Notepad if you wanted to. This is also works for CLJS on Node.js for which I wouldn't even have an idea how to make it work with my editor without setting my hair on fire (I've tried years ago, but it was just too brittle in my experience). If typing into the console gets tedious you can just use load-file to load a snippet from a file. Console REPL isn't optimal but I'm happy it's there when I need it.

😃 1
👍 1
seancorfield13:11:29

Despite my general position, I agree with both @U04V15CAJ and @U11EL3P9U that there are times when a quick'n'dirty console REPL is useful for quickly trying stuff out in the context of a library or a project, and I typically use Rebel Readline for that. My dot-clojure repo has a :rebel alias.

greglook17:11:44

The main things I wish rebel had out-of-the-box (that I miss from lein's nrepl/reply setup) is interruptible evals, an init namespace, and pretty-printed results (see: https://github.com/greglook/whidbey). I've sort of cobbled together that feature set on top of it in one of my projects, so maybe I just need to standardize and release that. :thinking_face:

elliot17:11:43

Fascinating, thanks! I use vim-fireplace for fast reloading namespaces, but not really evaluating anything interactively. I guess I’m pretty behind on IDEs in general. Maybe this is finally time to start coding in Emacs? Will check out the editors, thanks!

lilactown17:11:18

I think vim-fireplace and other vim plugins also provide the ability to evaluate forms via a REPL connection

lilactown17:11:26

I've heard good things about Conjure

🔥 2
dharrigan18:11:27

I use Conjure. Tis the apoidea's modified hinge joint...

elliot03:11:36

Follow-up: emacs + cider is actually generally pretty great. TODO: check how it works for CLJS.

Steph Crown09:11:02

Hi guys. What Clojure library do you suggest I use to write tests that mock api requests?

cjohansen09:11:28

If you’re using clj-http, you can use https://github.com/cjohansen/parrot

Nikolas Pafitis13:11:43

Is there a library that strips hiccup forms and returns only the text content?

p-himik13:11:07

Just an attempt at it, without a careful consideration of potential corner cases:

(->> (tree-seq vector?
               (fn [[_tag maybe-attrs & children]]
                 (if (map? maybe-attrs)
                   children
                   (cons maybe-attrs children)))
               [:div {:class "x"} "y" [:span "z"] [:p 1]])
     (remove vector?)
     (map str))
=> ("y" "z" "1")

👍 1
p-himik13:11:28

The most robust approach is probably to render Hiccup into an HTML string and then use it with something like jsoup to extract just the text from it.

2
borkdude13:11:25

echo '{}' > package.json
npm install nbb react-server html-to-text

$ npx nbb -e "(require '[reagent.core :as r] '[reagent.dom.server :as sr] '[\"html-to-text\" :refer [convert]]) (def html (convert (sr/render-to-static-markup [:p \"dude\" [:p \"hello\"]]))) html"

"dude\n\nhello\n\n"
:)

💯 1
mdiin14:11:25

Or if you like zippers:

#!/usr/bin/env bb

(require '[clojure.zip :as z])

(defn extract-strings
  [h]
  (let [result (atom [])]
    (loop [z (z/vector-zip h)]
      (if (z/end? z)
        (z/root z)
        (do
          (when (string? (z/node z))
            (swap! result conj (z/node z)))
          (recur (z/next z)))))
    @result))

(let [hiccup-vector [:html
                     [:head
                      [:title "Test"]]
                     [:body
                      [:h1 "Yay!"]
                      [:div {:class "foobar"}
                       [:p {:class "text-class"} "Imagine a long paragraph here..."]]]]]
  (println (extract-strings hiccup-vector)))

🚀 1
p-himik14:11:40

That's not entirely correct because AFAIK Hiccup also accepts other values and not just strings - exactly why my example has [:p 1] there.

👍 1
borkdude14:11:58

that may end up in the wrong order perhaps, since z/next does depth first maybe?

mdiin14:11:33

Could be, could be. All valid concerns I admit 🙂

borkdude14:11:45

or it could be why it works correctly, haha

mdiin14:11:12

I think depth first is what provides the correct ordering, yes.

mdiin14:11:21

Maybe the test for (when (string? (z/node z)) …should be (when-not (map? (z/node z)) … instead, as I actually just intended to remove the attribute maps that way.

borkdude14:11:24

yes, I think you're right

mdiin14:11:53

> That’s not entirely correct because AFAIK Hiccup also accepts other values and not just strings - exactly why my example has [:p 1] there. Actually, the query was to extract the strings. So wouldn’t it be wrong to extract numbers 😛

p-himik14:11:32

> the query was to extract the strings "Text content". :)

p-himik14:11:48

In Hiccup 1 is text content in [:p 1].

borkdude14:11:48

map? is too broad, since it will also emit vectors and keywords

mdiin14:11:00

Ah, you’re right. 😂

mdiin14:11:37

Yep, I see.

borkdude14:11:33

probably you have to treat vector separately, and ignore the first keyword and optionally the second map? and then treat all the following non-collections as text nodes

borkdude14:11:51

which is pretty much what @U2FRKM4TW did

👍 1
mdiin14:11:11

Would (when-not (or (coll? …) (keyword? …)) do it as well?

borkdude14:11:43

is [:p :foo :bar] valid hiccup?

mdiin14:11:24

Hm… It could be. You know what, maybe the zippers just introduce more trouble than they are worth here 😄

mdiin14:11:41

But it was a fun diversion.

👍 1
分数指数幂15:11:19

Hi, where can I get detailed document about nrepl? I don't

分数指数幂15:11:01

I don't konw how to close the nrepl client:sweat_smile:

borkdude15:11:38

In case you can't find any docs, you can try to backward engineer it with https://github.com/lambdaisland/nrepl-proxy. ::)

borkdude15:11:46

oh I thought docs about the nREPL protocol... sorry :)

borkdude15:11:00

Just ctrl-D or ctrl-C will work probably

flowthing15:11:02

Well, I’m not sure myself. 🙂

athos15:11:26

Here is the document for the nrepl protocol itself https://nrepl.org/nrepl/1.0/ops.html

分数指数幂16:11:42

@U0508956F thank you, I have been read this part, there have document about close a session, but I want to close the client. 😀

分数指数幂16:11:17

@U4ZDX466T I want to programmatically nrepl api, sorry, I forget say that. thanks you.

1
borkdude16:11:04

you can just send a close message and end the client?

分数指数幂16:11:18

@U04V15CAJ you mean the clojure.java.api source file document?

分数指数幂16:11:58

https://nrepl.org/nrepl/1.0/ops.html there is document about close a session, but have no document about close a client.

borkdude16:11:45

The messages described in https://nrepl.org/nrepl/1.0/ops.html are sent from the client to the server

borkdude16:11:01

so if a client is done, it should send a close message to the server, wait for the reply and then e.g. exit

分数指数幂17:11:49

I am talking to an nREPL endpoint programmatically, you mean I should send a close session message to the server? and how to exit?

borkdude17:11:49

that's it, just a close message.

分数指数幂17:11:47

got it, thank you very much~

Sam Ritchie21:11:28

I have some code that generates numerical functions that needs to run in a hot loop; the function body is fast, and the next step is to make it so that the generated functions take and return JS arrays or java arrays instead of Clojure vectors, so I don’t have to convert back and forth from native. Does anyone know of a wrapper around native arrays that allows them to be destructured, but is still very fast? If so I wouldn’t have to change anything at all

Sam Ritchie21:11:56

Otherwise I’ll write a transform to convert between

(fn [[t theta thetadot]]
  [1.0 thetadot (* -9.8 (Math/sin theta))])

(fn [xs]
  #js [1.0 (aget xs 2) (* -9.8 (Math/sin (aget xs 1)))])

hiredman22:11:24

user=> ((fn [[a]] a) (doto (object-array 1) (aset 0 :a)))
:a
user=>

hiredman22:11:54

that is in clojure, dunno about clojurescript

dpsutton22:11:17

❯ clj -A:cljs -M -m cljs.main -re node -r
ClojureScript 1.10.773
cljs.user=> ((fn [[a]] a) (doto (object-array 1) (aset 0 :a)))
:a
cljs.user=>

Sam Ritchie22:11:50

nice! is this slower perf-wise than aget?

Sam Ritchie22:11:00

i’ll test it out here too

hiredman22:11:09

which is RT.nth, which does a check if it is an array and then does an aget

hiredman22:11:47

I suspect it will be slower and involve boxing and unboxing for arrays of primitives, but maybe pretty close to the same for arrays of objects

Sam Ritchie22:11:27

ah on jvm good call about boxing. maybe no issue in js though

Sam Ritchie22:11:06

@U0NCTKEV8 50x faster on the second one

(fn [[_t theta thetadot]]
  [1.0 thetadot (* -9.8 (Math/sin theta))])

(fn [^doubles arr]
  [1.0 (aget arr 2) (* -9.8 (Math/sin (aget arr 1)))])

Sam Ritchie22:11:10

Here is a related question: Why is this

(defn faster [^doubles arr]
  (let [t        (aget arr 0)
        theta    (aget arr 1)
        thetadot (aget arr 2)]
    [1.0 thetadot (* -9.8 (Math/sin theta))]))
5x faster, seemingly, than
(defn faster [^doubles arr]
  (let [t        (aget arr 0)
        theta    (aget arr 1)
        thetadot (aget arr 2)]
    (double-array [1.0 thetadot (* -9.8 (Math/sin theta))])))
I guess I would have thought building the vector would be slower than building a double array?

dpsutton22:11:02

aren’t you building a vector in both versions? The difference is that you take that vector and get a double-array from it after the vector is build?

Sam Ritchie22:11:00

@U11BV7MTK I had thought from… wait, yes, of course you’re right.

(defn faster [^doubles arr]
  (let [t        (aget arr 0)
        theta    (aget arr 1)
        thetadot (aget arr 2)
        result (double-array 3)]
    (aset result 0 1.0)
    (aset result 1 thetadot)
    (aset result 2 (* -9.8 (Math/sin theta)))))

hiredman22:11:25

use doto

👍 1
Sam Ritchie22:11:40

;; 
(defn ->vec [^doubles arr]
  (let [t        (aget arr 0)
        theta    (aget arr 1)
        thetadot (aget arr 2)]
    [1.0 thetadot (* -9.8 (Math/sin theta))]))

(defn ->array [^doubles arr]
  (let [t        (aget arr 0)
        theta    (aget arr 1)
        thetadot (aget arr 2)]
    (doto (double-array 3)
      (aset 0 1.0)
      (aset 1 thetadot)
      (aset 2 (* -9.8 (Math/sin theta))))))

(let [xs (double-array [0 1 2])]
  (time (dotimes [_ 100000000]
          (->vec xs)))

  (time (dotimes [_ 100000000]
          (->array xs))))
“Elapsed time: 469.831042 msecs” “Elapsed time: 929.422125 msecs”

Sam Ritchie22:11:46

still 2x faster to make a vector!

Sam Ritchie22:11:16

haha but then if I need to READ from the data later and include that in the benchmark (which I do for my animation case)

(defn vec-checker [arr]
  (let [result (->vec arr)]
    (= (result 1)
       (result 2))))

(defn array-checker [arr]
  (let [result ^doubles (->array arr)]
    (= (aget result 1)
       (aget result 2))))

(let [xs (double-array [0 1 2])]
  (time (dotimes [_ 100000000]
          (vec-checker xs)))

  (time (dotimes [_ 100000000]
          (array-checker xs))))
“Elapsed time: 1188.148417 msecs” “Elapsed time: 896.090459 msecs”

Sam Ritchie22:11:08

(each of these lines on the right is a physics simulation… these perf changes let 256 of these run at 60fps no sweat, vs my old lazy version)

hiredman23:11:33

double-array is overloaded, it can take a seqable of initial values or a size, and it has to do an instance check

hiredman23:11:14

and for the literal vector, I believe for a vector that small it is just allocating an array to hold the items

Sam Ritchie23:11:06

(doto ^doubles (make-array Double/TYPE  3)
      (aset 0 1.0)
      (aset 1 thetadot)
      (aset 2 (* -9.8 (Math/sin theta))))
this makes the array version the same speed

hiredman23:11:59

(was looking and forgot about make-array)

Sam Ritchie23:11:29

aset-double on the other hand is ultra slow; I guess because it’s coercing its argument, which I don’t need?

hiredman23:11:34

I dunno, I think that is very possible, the variable arity version of aset-double would also be slow

Ben Sless04:11:04

You can also set unchecked maths to shave a few ns more

👍 1
Ben Sless09:11:01

Naive question: why send in an array and return an array? Can it be inlined so that you don't have to allocate a new array at all?

Sam Ritchie16:11:24

@UK0810AQ2 not naive, great question… in the JS version of the ODE solver, the interface just happens to want you to return an array. but I could send a patch to the library to supply a single output array and the function could just set the values. That would definitely be an improvement: https://github.com/littleredcomputer/odex-js#a-system-of-two-first-order-equations and then in the Java version, of COURSE now that I look more closely of course the code supplies an output array https://commons.apache.org/proper/commons-math/javadocs/api-3.6.1/org/apache/commons/math3/ode/FirstOrderDifferentialEquations.html

Sam Ritchie16:11:08

well, looks like there are some monster performance wins on the table here. Thanks all for talking me through this!!

Ben Sless17:11:26

Also change out swap inc with an atomic integer, should be friendlier to the CPU

❤️ 1
Ben Sless17:11:12

And flatten is evil

👍 1
Sam Ritchie17:11:59

The original code is written with these nested vector like objects, so my first pass a year ago did these dirty back and forth swaps

Sam Ritchie17:11:23

But now my compilation code is better, so I’ll add a new mode for the ODE-solver-only version

Sam Ritchie17:11:45

And then if the user specifically needs a callback with the immutable nested version, great

chrisn18:11:26

@U017QJZ9M7W - tmdjs had nice wrappers around the typed arrays. See the api docs for tech.v3.datatype in tmdjs

chrisn18:11:23

They include nth support , reduction, and fast sub-buffer and such.

chrisn19:11:06

A lot of your examples that use clojure.core/double-array -- ham-fisted.api/double-array is faster. The seq creation is unnecessary and a slower form of access than a reduction maintaining the index.

chrisn19:11:19

In general creating a ton of small arrays or small vectors is a I think bad design paradigm performance wise. You should be creating one 2D tensor and read/write to it's rows. Then you aren't creating a lot of garbage every frame.

👍 2
chrisn19:11:59

OR just one or two double arrays and write to offsets within the array. You have a fixed number of particles...

Sam Ritchie19:11:18

Yes that is the change I’ll make here, and it should always work

chrisn20:11:55

Hmm - in my tests, reading from large double array and writing to another isn't a ton faster although in clojurescript it is. The JVM really is pretty amazing. Parallelizing the computation makes the no-gc pathway shine but serially it performs roughly the same so it depends on how loaded you intend to make the machine. Vectorizing it with jdk-17+ vecops would make a difference and that is where your compiler could really shine.

Sam Ritchie19:11:13

@UDRJMEFSN super weird, it looks like in cljs it is way faster to return #js [..] arrays in my tests than it is to recycle a single output array with aset

chrisn13:11:06

Did you try using typed arrays in js? I thought #js created generic object arrays.

shaunlebron23:11:53

Are clojure files executable? The tools.cli example shows a my.program namespace that is executed somehow by a my-program command: https://github.com/clojure/tools.cli#quick-start how does it work?

my-program -vvvp8080 foo --help --invalid-opt

hiredman23:11:26

the answer is complicated

hiredman23:11:34

clojure does actually support shebang lines as comments at the top like that for scripts, but basically no one uses them, and there isn't really good support for them anywhere

hiredman23:11:19

I suspect the docs mean for you to do something like

${whatever you do to run clojure programs} -vvvp8080 foo --help --invalid-opt

1
hiredman23:11:38

since you might uses lein run or clj, or whatever

hiredman23:11:22

when you use clojure as a shebang script it doesn't call a main, it just loads the code given, so you need to have toplevel side effects if you want to ever do anything

hiredman23:11:50

you can also do some wild stuff to have a shell script that is also valid clojure

hiredman23:11:18

https://gist.github.com/hiredman/a8fb63ec64704ecb967f is a script I used to use to delete old database backups off s3

hiredman23:11:16

a shebang like that only works on osx, because different unix like systems handle arguments in shebangs differently

shaunlebron23:11:30

Alright, I guess this is what babashka is for

shaunlebron23:11:42

Thank you for the detailed answers and ideas

skylize23:11:01

If you have a strong need for a native executable, you could compile with GraalVM. But if you are just wanting to use Clojure syntax for scripting, then Babashka is definitely the way to go.

shaunlebron23:11:16

to answer my own question, we can create a ./my-program file with this I guess:

#!/bin/sh
clojure -M -m my.program "$@"

shaunlebron23:11:06

I’m making liberal use of clojure libs and I’m not sure if it’ll work with babashka, but I haven’t used it much yet

seancorfield00:11:49

@shaunlebron I've updated the Quick Start to say: Execute the command line:

clojure -M -m my.program -vvvp8080 foo --help --invalid-opt
(or use lein run or however you run your program instead of clojure -M -m my.program)

👍 2
seancorfield00:11:19

(thanks for the feedback that it was confusing as written!)

Joshua Suskalo16:11:03

There's also this, which allows you to make a shebang script that does a clojure shell script (which like mentioned before must have top-level side effects) and which can include dependencies, pass command line arguments, etc.

shaunlebron23:11:37

I tried adding #!/usr/bin/env clojure -M to the top of my clojure file, but it didn’t execute the -main function

seancorfield00:11:49

@shaunlebron I've updated the Quick Start to say: Execute the command line:

clojure -M -m my.program -vvvp8080 foo --help --invalid-opt
(or use lein run or however you run your program instead of clojure -M -m my.program)

👍 2