clerk

Nguyen Hoai Nam 2024-10-18T07:39:53.875369Z

I’ve got some questions related to Clerk synced atoms. How much data can (or should) each atom contain? I’m asking this because earlier today I tried to sync a vector of 10000 maps (basically tabular data with 10000 rows), and Clerk displayed a Method code too large! error. I dug deeper and realized that: • The atom watch on the SCI side sends a :swap! message with the new value inlined in a fn body. For large edn values, the fn body blows up and (probably) can’t be compiled. • In clerk.webserver/ws-handlers, the :swap! handler is like this: (apply swap! @var (eval (:args msg)))). eval can’t handle large quoted forms. (I tested this by calling it with a string literal containing 100000 e characters.) Am I misusing Clerk synced atoms? And if I’m not, would it be a good idea for the handler to be something like (reset! @var (first (:args msg))))?

Nguyen Hoai Nam 2024-10-21T01:26:32.111749Z

Wow, lots of things happened while I was away.

Nguyen Hoai Nam 2024-10-21T01:29:13.368219Z

I can confirm that commit 336167c fixed my initial issue. Thank you so much!

mkvlr 2024-10-18T08:27:17.365919Z

it’s not optimized yet so currently works for small things only

mkvlr 2024-10-18T08:27:40.312189Z

I should add diffing, currently we send the whole new state on a change

borkdude 2024-10-18T08:44:07.446929Z

Are you by any chance also using hiccup around this data? It’s also a common thing with the hiccup library since the html macro tends to explode the code

Nguyen Hoai Nam 2024-10-18T08:53:30.489939Z

The data itself isn’t in Hiccup syntax. I made sure of this by using pr-str to convert the edn data to a giant string, and Clerk didn’t manage to swap! it. At the moment I’m working around this by redefining clerk.webserver/ws-handlers and clerk.render/atom-changed, and it’s working. The giant string is going through. It seems that the solution is just to avoid having the giant value be nested in a fn or eval form.

mkvlr 2024-10-18T08:59:39.182349Z

can you paste a small repro?

Nguyen Hoai Nam 2024-10-18T09:03:22.535029Z

Absolutely, but I need some time, if you don’t mind. I’m still in the middle of work.

mkvlr 2024-10-18T09:07:46.875929Z

sure, no rush, thank you

Nguyen Hoai Nam 2024-10-18T10:00:48.163159Z

@mkvlr Thanks for the wait. This notebook should produce the following error lines:

Caused by: Syntax error compiling fn* at (0:0).
...
Caused by: java.lang.IndexOutOfBoundsException: Method code too large!
...
Let me know if you see something similar.

borkdude 2024-10-18T10:26:29.293559Z

I can reproduce this. I tried to change the form from (fn [_] data) to (let [data ...] (fn [_] data)) which didn't help. just eval with a large blob of data seems to behave like this. I tried with sci.core/eval-form which did work for a large blob of data

borkdude 2024-10-18T10:27:17.781259Z

but perhaps introducing a new operation :reset! instead of :swap! would also help, where you don't have to push the data through eval

borkdude 2024-10-18T10:37:12.521399Z

This at least fixes this particular case for me: https://github.com/nextjournal/clerk/pull/710

Nguyen Hoai Nam 2024-10-18T10:38:13.040109Z

My local workaround right now is exactly the same as your PR. I haven’t tried sci-core/eval-form though. I might try it later.

borkdude 2024-10-18T10:39:01.854369Z

sci.core you need to add this library which I did with (clojure.repl.deps/add-lib 'borkdude/sci) in the REPL

👍 1
borkdude 2024-10-18T11:35:50.629119Z

@nam_nguyenhoai you can use clerk with :git/sha 39da39689570d8efb841ec5849761a8529f1f6a3 in your own project to test the PR, without building your own custom CLJS

Nguyen Hoai Nam 2024-12-26T08:32:34.371669Z

@mkvlr I’m so sorry, I’ve been working on something else and just returned to my Clerk project today. There seems to be an issue with Clerk’s rerendering (see my latest message in #clerk).

mkvlr 2024-10-22T18:44:57.938539Z

https://github.com/nextjournal/clerk/commit/748f73e4958ac5131889196ff81635b3f83a90f5 so working with large ⚛️ sync atoms should me much faster now.

🎉 2
mkvlr 2024-10-21T13:18:03.048539Z

https://github.com/nextjournal/clerk/pull/722 should speed things up by a lot

mkvlr 2024-10-21T13:18:35.094209Z

@nam_nguyenhoai would be great if you could take it for a spin and let me know if it works for you and if you notice any other issue with it

👍 1
⏳ 1
mkvlr 2024-10-19T12:27:00.740309Z

I worked on this during my transatlantic flight, I pushed two things to main: • fixing it for large values https://github.com/nextjournal/clerk/commit/336167c858d9640710092f564ac7a76cb2834c1e (same change both of you did, sorry for the overlap) • sending editscript diffs instead of the whole value for atom changes (clerk was already doing this for the other direction for notebooks) https://github.com/nextjournal/clerk/commit/233541b5bcc3e6dc1ac7d565a41333bd07f12b07

mkvlr 2024-10-19T12:28:40.485609Z

this works now but the recompute performance is still pretty poor

mkvlr 2024-10-19T12:29:00.220339Z

fixing this is a bit more involved, I’ve started on https://github.com/nextjournal/clerk/compare/delay-viewer-eval?expand=1

mkvlr 2024-10-19T12:33:50.555849Z

we ran into issues caused by the eval on read a number of times, right @borkdude? Though overnight I had the idea that we could also delay evaluation by implementing viewer-eval on top of viewer-fn (invoke the viewer fn does the eval).

borkdude 2024-10-19T12:34:13.314209Z

doing eval when reading always felt wrong to me

borkdude 2024-10-19T12:34:55.164469Z

but so far we got away with it

mkvlr 2024-10-19T12:39:42.442379Z

in the change above I’m doing it explicitly in a postwalk step

mkvlr 2024-10-19T12:39:58.460359Z

and moving away from the defprotocol things

mkvlr 2024-10-19T12:40:37.005939Z

not sure that this is good though

mkvlr 2024-10-19T12:41:13.657889Z

maybe the defprotocol stuff is good but we should use it a bit differently (like not eval on read)

borkdude 2024-10-19T12:41:55.678829Z

what's the problem you're trying to solve exactly here?

borkdude 2024-10-19T12:42:33.579989Z

ah yes

mkvlr 2024-10-19T12:45:26.934239Z

(defn eval-cljs
  ([form] (eval-cljs {} form))
  ([viewer-opts form]
   ;; because ViewerEval's are evaluated at read time we can no longer
   ;; check after read if there was any in the doc. Thus we set the
   ;; `:nextjournal.clerk/remount` attribute to a hash of the code (so
   ;; it changes when the code changes and shows up in the doc patch.
   ;; TODO: simplify, maybe by applying Clerk's analysis to the cljs
   ;; part as well
   (with-viewer (-> viewer-eval-viewer
                    (update :transform-fn comp maybe-rewrite-cljs-form-for-cherry)
                    (assoc :nextjournal.clerk/remount (hash-sha1 form)))
     viewer-opts
     (->viewer-eval form))))

mkvlr 2024-10-19T12:45:32.914009Z

this is also a related problem

borkdude 2024-10-19T12:46:00.814529Z

yes, it seems better to have a separate eval phase

borkdude 2024-10-19T12:46:32.096979Z

or perhaps a delayed eval like your PR is called

mkvlr 2024-10-19T12:47:28.594669Z

do you have a preference for a custom type like we have now vs something simpler (tagged data)?

mkvlr 2024-10-19T12:48:10.009689Z

i thought I simplified things by moving to tagged data [tag opts form]

borkdude 2024-10-19T12:48:17.278519Z

seems like an unimportant detail to me

mkvlr 2024-10-19T12:48:20.865469Z

but not sure anymore

mkvlr 2024-10-19T12:48:39.465219Z

that helps, thanks

mkvlr 2024-10-19T12:49:39.824629Z

dang, why didn’t I think of the fact that eval could be delayed leaving stuff mostly as is yesterday

borkdude 2024-10-19T12:50:11.620959Z

because you're on a plane? ;)

mkvlr 2024-10-19T12:50:15.562919Z

that’s the downside of having too much time on the plane, right

borkdude 2024-10-19T12:50:17.006899Z

or having jetlag? ;)

borkdude 2024-10-19T12:50:34.283459Z

at least you killed some time :)

mkvlr 2024-10-19T12:55:05.645689Z

so if we do this:

(defn ->viewer-fn [form]
  (map->ViewerFn {:form form
                  #?@(:cljs [:f (delay (eval form))])}))

mkvlr 2024-10-19T12:55:30.925439Z

we should still be able to bind *eval* to a cherry evaluator when we invoke the fn, is that correct?

borkdude 2024-10-19T12:56:05.698259Z

yes, but note that theoretically any form could also return a delay so you need to distinguish between your delay and a returned delay perhaps

mkvlr 2024-10-19T12:56:19.110669Z

yes

mkvlr 2024-10-19T12:56:30.329479Z

will deref on invoke

mkvlr 2024-10-19T12:56:39.183509Z

cool

borkdude 2024-10-19T12:57:20.445649Z

oh right, yes, (delay (eval '(delay ...)) will work out ok

borkdude 2024-10-19T12:57:29.195849Z

just not deref twice

mkvlr 2024-10-19T12:57:41.463479Z

yes

mkvlr 2024-10-19T13:06:31.626619Z

hmm, seems simple enough but isn’t working. https://github.com/nextjournal/clerk/commit/0be230bcfe965bf19fe20b1277f37db877008144 Do you see anything wrong?

mkvlr 2024-10-19T13:08:11.969709Z

I hate these failure modes

borkdude 2024-10-19T13:09:08.815199Z

I'd have to run that locally, but I'm afk soon

borkdude 2024-10-19T13:09:33.184119Z

can look Monday with some more focus

mkvlr 2024-10-19T13:10:15.581519Z

cool, I’ll write if I get futher until then

borkdude 2024-10-19T13:11:14.007409Z

pro tip: don't do a demo with an experimental PR

😅 1
mkvlr 2024-10-19T13:11:15.312809Z

found it 🙃

👍 1
borkdude 2024-10-19T13:12:34.764249Z

👍

peterh 2024-10-18T21:47:41.113389Z

Eager to try the new render REPL from the recent update and connect to it from CIDER, I followed the instructions on https://github.com/mk/clerk-render-repl exactly (using a clone of the repo as a test, after trying it with a minimal Clerk project, with which I had the same issues). Everytime I run cider-connect-cljs with the parameters mentioned, my Emacs freezes and after I send pkill -USR2 Emacs to unfreeze it from the console, I see the following error message in the CIDER repl for the CLJ session:

[http-kit-server-worker-3] ERROR - handle websocket frame org.httpkit.server.Frame$TextFrame@175f1681
java.lang.RuntimeException: No reader function for tag object
	at clojure.lang.EdnReader$TaggedReader.readTagged(EdnReader.java:801)
	at clojure.lang.EdnReader$TaggedReader.invoke(EdnReader.java:783)
	at clojure.lang.EdnReader$DispatchReader.invoke(EdnReader.java:549)
	at clojure.lang.EdnReader.readDelimitedList(EdnReader.java:757)
	at clojure.lang.EdnReader$MapReader.invoke(EdnReader.java:680)
	at clojure.lang.EdnReader.read(EdnReader.java:145)
	at clojure.lang.EdnReader.read(EdnReader.java:111)
	at clojure.lang.EdnReader.readString(EdnReader.java:67)
	at clojure.edn$read_string.invokeStatic(edn.clj:46)
	at clojure.edn$read_string.invokeStatic(edn.clj:37)
	at clojure.edn$read_string.invoke(edn.clj:37)
	at sci.nrepl.browser_server$response_handler.invokeStatic(browser_server.clj:50)
	at sci.nrepl.browser_server$response_handler.invoke(browser_server.clj:49)
	at sci.nrepl.browser_
Regardless of the error, the ClojureScript REPL works fine and I can evaluate JS commands which get logged in the browser console in the notebook. I can also evaluate forms from Emacs using CIDER commands (however, I have to switch to the CLJ repl buffer to evaluate forms in .clj files and back to the CLJS repl buffer to evaluate forms in .cljs files, which is a bit annoying). Even though it kind-of works, the error and the fact that I had to send a signal to Emacs to unfreeze it (making my Emacs behave weirdly afterwards) makes it unusable for me. Is there something I can do to fix the error No reader function for tag object (whatever this means) or does anyone have an idea what the cause could be? I am using the latest CIDER version. EDIT/Update: after removing the Cider extension clj-refactor.el, it works just fine with no errors, so this was probably the cause.

borkdude 2024-10-18T21:50:13.497309Z

I remember having this error using an old(er) version of cider.el once. Perhaps it's just a matter of upgrading

borkdude 2024-10-18T21:52:35.973479Z

Also the repo you're pointing at should probably not be used as a reference, it's 3 months old and just a test at the time. Things have changed since then

borkdude 2024-10-18T21:57:27.581529Z

The way you should connect: Add :render-nrepl to serve! > - :render-nrepl opt into starting a render nREPL server into Clerk's render environment running in the browser, pass {} to start it on the default port or pass a custom :port number. And then from emacs (and I'm having trouble finding this in the docs @mkvlr btw, but perhaps this is because it's just how it's done by default):

cider-connect-cljs
select port
select repl type: nbb
sesman-link-with-buffer

borkdude 2024-10-18T21:59:02.822259Z

If you still have problems doing it that way, please create a step by step repro and I'll have a look next Monday

peterh 2024-10-18T22:25:18.900479Z

I am using CIDER 1.16.0, which seems to be the latest stable version. Before trying it with the cloned repo, I did set up a minimal Clerk project using :render-nrepl in serve! and cider-connect-cljs with nbb as you mentioned and got the same error. Actually, already in the browser console (while Emacs freezes), I get

Unhandled clojure.lang.ExceptionInfo
Clerk encountered the following error attempting to read an incoming message: No reader function for tag object
{:message 
"
"
{:type :nrepl, :msg {:prefix-rewriting "false", :ns "user", :err "unknown-op", :"
0 more…"}
which seems to point to the same problem. I'll post a step-by-step to reproduce the error, maybe there is something wrong with the setup of my repo or my Cider config or whatever. Thanks!

peterh 2024-10-19T00:11:33.924839Z

Update: after removing the Cider extension clj-refactor.el, it works just fine with no errors! I guess I’ll leave it uninstalled for now, haven’t used it much anyways.

👍 2
borkdude 2024-10-19T07:45:49.816819Z

Good to know

mkvlr 2024-10-18T06:44:20.479199Z

My three-minute lightning talk “What’s new in Clerk” from Heart of Clojure is now up: https://www.youtube.com/watch?v=07k0IFviazg

🎉 2
danieroux 2024-10-18T07:39:55.867529Z

I was sad to see it was only three minutes 😆 More Clerk content makes more happy.

🙃 1