Fork me on GitHub
#clerk
<
2023-03-23
>
zane01:03:15

Hey friends! Regarding long-running computations, I’ve been wondering whether it might make sense to make use of clojure.core/delay in combination with a custom viewer. 🧵

zane01:03:59

My goals are to: 1. Reduce the delay before the notebook renders. 2. Give the user control of when to kick off the long-running computation.

zane01:03:36

Here’s a first attempt at the viewer that mostly works:

(def delay
  {:pred delay?

   :var-from-def? true

   :transform-fn
   (comp clerk/mark-presented
         (clerk/update-val (fn [{::clerk/keys [var-from-def]}]
                             (let [realized (realized? @var-from-def)]
                               (cond-> {:delay @var-from-def
                                        :var-name (symbol var-from-def)
                                        :realized realized}
                                 realized (assoc :value @@var-from-def))))))

   :render-fn
   '(fn [{:keys [delay var-name realized value]}]
      (if realized
        (nextjournal.clerk.render/inspect value)
        (reagent.core/with-let [!evaluating (reagent.core/atom false)]
          (nextjournal.clerk.viewer/html
           (if @!evaluating
             "Loading..."
             [:<>
              [:button {:type "button"
                        :on-click #(when-not @!evaluating
                                     (reset! !evaluating true)
                                     (nextjournal.clerk.viewer/clerk-eval
                                      `(force ~var-name)))}
               "Eval"]
              (nextjournal.clerk.render/inspect delay)])))))})

zane01:03:30

Clicking the button to force the delay seems to work, and for simple expressions like (range 10) the result gets rendered to the page once the delay is realized. But for more complex expressions, like charts from vega/vl, nothing gets rendered to the page when after the delay is realized. :thinking_face:

zane01:03:52

I imagine my issue is something simple. Perhaps I’m misusing nextjournal.clerk.render/inspect?

Sam Ritchie03:03:35

Any console warnings, out of curiosity?

zane05:03:05

Nope! No warnings.

zane07:03:18

Am I taking the wrong approach here?

mkvlr13:03:58

@U050CT4HR here’s an approach that works:

(ns scratch
  (:require [nextjournal.clerk :as clerk]))

(def future-viewer
  {:pred future?
   :transform-fn (clerk/update-val (fn [fut] (if (realized? fut)
                                              @fut
                                              (clerk/html [:p "Loading…"]))))})

(clerk/add-viewers! [future-viewer])

(def my-future
  (future (Thread/sleep 3400)
          (future (clerk/recompute!))
          (clerk/vl {:width 650 :height 400 :data {:url ""
                                                   :format {:type "topojson" :feature "counties"}}
                     :transform [{:lookup "id" :from {:data {:url ""}
                                                      :key "id" :fields ["rate"]}}]
                     :projection {:type "albersUsa"} :mark "geoshape" :encoding {:color {:field "rate" :type "quantitative"}}
                     :embed/opts {:actions false}})))

mkvlr13:03:25

doing this kind of stuff with only :transform-fn

mkvlr13:03:58

and think recomputing when it’s done is more what you want probably, so not needing a button?

mkvlr13:03:48

or here with a small macro

(ns scratch
  (:require [nextjournal.clerk :as clerk]))

(def future-viewer
  {:pred #(-> % meta ::future-compute)
   :transform-fn (clerk/update-val (fn [fut] (if (realized? fut)
                                              @fut
                                              (clerk/html [:p "Loading…"]))))})

(clerk/add-viewers! [future-viewer])

(defmacro future-compute [& body]
  "Like `clojure.core/future` but for computing a result for Clerk in the background."
  `(with-meta (future (let [res# @(future ~@body)]
                        (future (clerk/recompute!))
                        res#))
     {::future-compute true}))

(def my-future
  (future-compute (Thread/sleep 3400)
                  (clerk/vl {:width 650 :height 400 :data {:url ""
                                                           :format {:type "topojson" :feature "counties"}}
                             :transform [{:lookup "id" :from {:data {:url ""}
                                                              :key "id" :fields ["rate"]}}]
                             :projection {:type "albersUsa"} :mark "geoshape" :encoding {:color {:field "rate" :type "quantitative"}}
                             :embed/opts {:actions false}}))
  )

mkvlr13:03:51

or with the first rule of macro club applied:

(ns scratch
  (:require [nextjournal.clerk :as clerk]))

(def future-viewer
  {:pred #(-> % meta ::future-compute)
   :transform-fn (clerk/update-val (fn [fut] (if (realized? fut)
                                              @fut
                                              (clerk/html [:p "Loading…"]))))})

(clerk/add-viewers! [future-viewer])

(defn recompute-when-realized [fut]
  (with-meta (future (let [res @fut]
                       (future (clerk/recompute!))
                       res))
    {::future-compute true}))

(def my-future
  (recompute-when-realized
   (future (Thread/sleep 3400)
           (clerk/vl {:width 650 :height 400 :data {:url ""
                                                    :format {:type "topojson" :feature "counties"}}
                      :transform [{:lookup "id" :from {:data {:url ""}
                                                       :key "id" :fields ["rate"]}}]
                      :projection {:type "albersUsa"} :mark "geoshape" :encoding {:color {:field "rate" :type "quantitative"}}
                      :embed/opts {:actions false}}))))

zane15:03:43

Amazing. Thanks, @U5H74UNSF!

zane20:03:17

> so not needing a button? Actually, no. As I mentioned above, part of the goal is to delay kicking off the execution of the expensive computation until the reader explicitly triggers it.

zane20:03:32

But I think I can see how to combine what I had with what you wrote. Will try and circle back.

👍 2
zane01:03:28

This does the trick.

{::clerk/viewer
 {:var-from-def? true
  :transform-fn
  (comp clerk/mark-presented
        (clerk/update-val
         (fn [{:nextjournal.clerk/keys [var-from-def]}]
           (let [var-symbol (symbol var-from-def)
                 delay @var-from-def
                 realized (realized? delay)]
             (cond-> {:var-symbol var-symbol
                      :delay (viewer/present delay)
                      :realized realized}
               realized (assoc :val (viewer/present @delay)))))))

  :render-fn
  '(fn [{:keys [realized delay var-symbol val]}]
     (if realized
       (nextjournal.clerk.render/inspect-presented val)
       (reagent.core/with-let [!evaluating (reagent.core/atom false)]
         (nextjournal.clerk.viewer/html
          (if @!evaluating
            [:p "Loading..."]
            [:<>
             [:button {:type "button"
                       :on-click #(when-not @!evaluating
                                    (reset! !evaluating true)
                                    (nextjournal.clerk.viewer/clerk-eval
                                     `(force ~var-symbol)))}
              "Eval"]
             (nextjournal.clerk.render/inspect-presented delay)])))))}}

zane01:03:10

My issue was that I never presented the value from the delay.

zane03:03:15

Here’s an attempt at a future viewer that supports restarting:

{:pred future?
 :var-from-def? true
 :transform-fn
 (comp clerk/mark-presented
       (clerk/update-val
        (fn [{:nextjournal.clerk/keys [var-from-def]}]
          (assert (var? var-from-def))
          (let [var-symbol (symbol var-from-def)
                future @var-from-def
                cancelled (future-cancelled? future)
                done (future-done? future)]
            (cond-> {:var-symbol var-symbol
                     :future (viewer/present future)
                     :done done
                     :cancelled cancelled}
              (and (not cancelled) done)
              (assoc :val (viewer/present @future)))))))

 :render-fn
 '(fn [{:keys [future var-symbol val done cancelled]}]
    (cond cancelled
          [:button {:type "button"
                    :on-click #(nextjournal.clerk.viewer/clerk-eval
                                `(nextjournal.clerk/clear-cache! '~var-symbol))}
           "Restart"]

          done (nextjournal.clerk.render/inspect-presented val)

          :else
          [:<>
           [:button {:type "button"
                     :on-click #(nextjournal.clerk.viewer/clerk-eval
                                 `(future-cancel ~var-symbol))}
            "Cancel"]
           (nextjournal.clerk.render/inspect-presented future)]))}

chromalchemy05:03:25

I am getting an error when I try to run a function that wraps a Specter fn. This user fn takes a Specter path (which is a vector of navigator functions/symbols). Getting Execution error (ExceptionInfo) at clojure.tools.analyzer/parse-var (analyzer.clj:803) var not found: custom-path I have {:nextjournal.clerk/error-on-missing-vars :off} below my namespace name. And normal specter code seems to be working. This used to be working. Is there a type hint that might help out?

jackrusher09:03:43

Can you make a minimal reproduction so we can investigate?

chromalchemy14:03:17

I couldn’t reproduce in a minimal repo. Seems to be working the way I normally wrap a fn. Here is part of the stack trace from the Clerk view, from my original project, for context. I will investigate further.

Unhandled clojure.lang.ExceptionInfo
var not found: custom-path
{:var custom-path}
analyzer.clj:	803	clojure.tools.analyzer/parse-var
analyzer.clj:	792	clojure.tools.analyzer/parse-var
analyzer.clj:	809	clojure.tools.analyzer/-parse
analyzer.clj:	805	clojure.tools.analyzer/-parse
jvm.clj:	427	clojure.tools.analyzer.jvm/parse
jvm.clj:	424	clojure.tools.analyzer.jvm/parse
analyzer.clj:	271	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	127	clojure.tools.analyzer/analyze-in-env/fn--4416
core.clj:	6979	clojure.core/mapv/fn--8535
protocols.clj:	168	clojure.core.protocols/fn--8249
protocols.clj:	124	clojure.core.protocols/fn--8249
protocols.clj:	19	clojure.core.protocols/fn--8204/G--8199--8213
protocols.clj:	31	clojure.core.protocols/seq-reduce
protocols.clj:	75	clojure.core.protocols/fn--8234
protocols.clj:	75	clojure.core.protocols/fn--8234
protocols.clj:	13	clojure.core.protocols/fn--8178/G--8173--8191
core.clj:	6886	clojure.core/reduce
core.clj:	6970	clojure.core/mapv
core.clj:	6970	clojure.core/mapv
analyzer.clj:	781	clojure.tools.analyzer/parse-invoke
analyzer.clj:	777	clojure.tools.analyzer/parse-invoke
analyzer.clj:	809	clojure.tools.analyzer/-parse
analyzer.clj:	805	clojure.tools.analyzer/-parse
jvm.clj:	427	clojure.tools.analyzer.jvm/parse
jvm.clj:	424	clojure.tools.analyzer.jvm/parse
analyzer.clj:	271	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	127	clojure.tools.analyzer/analyze-in-env/fn--4416
core.clj:	6979	clojure.core/mapv/fn--8535
PersistentVector.java:	343	clojure.lang.PersistentVector
core.clj:	6885	clojure.core/reduce
core.clj:	6970	clojure.core/mapv
core.clj:	6970	clojure.core/mapv
analyzer.clj:	183	clojure.tools.analyzer/analyze-vector
analyzer.clj:	180	clojure.tools.analyzer/analyze-vector
analyzer.clj:	50	clojure.tools.analyzer/eval4369/fn--4370
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	127	clojure.tools.analyzer/analyze-in-env/fn--4416
core.clj:	6979	clojure.core/mapv/fn--8535
protocols.clj:	168	clojure.core.protocols/fn--8249
protocols.clj:	124	clojure.core.protocols/fn--8249
protocols.clj:	19	clojure.core.protocols/fn--8204/G--8199--8213
protocols.clj:	31	clojure.core.protocols/seq-reduce
protocols.clj:	75	clojure.core.protocols/fn--8234
protocols.clj:	75	clojure.core.protocols/fn--8234
protocols.clj:	13	clojure.core.protocols/fn--8178/G--8173--8191
core.clj:	6886	clojure.core/reduce
core.clj:	6970	clojure.core/mapv
core.clj:	6970	clojure.core/mapv
analyzer.clj:	781	clojure.tools.analyzer/parse-invoke
analyzer.clj:	777	clojure.tools.analyzer/parse-invoke
analyzer.clj:	809	clojure.tools.analyzer/-parse
analyzer.clj:	805	clojure.tools.analyzer/-parse
jvm.clj:	427	clojure.tools.analyzer.jvm/parse
jvm.clj:	424	clojure.tools.analyzer.jvm/parse
analyzer.clj:	271	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	503	clojure.tools.analyzer/analyze-let
analyzer.clj:	490	clojure.tools.analyzer/analyze-let
analyzer.clj:	528	clojure.tools.analyzer/parse-let*
analyzer.clj:	523	clojure.tools.analyzer/parse-let*
analyzer.clj:	809	clojure.tools.analyzer/-parse
analyzer.clj:	805	clojure.tools.analyzer/-parse
jvm.clj:	427	clojure.tools.analyzer.jvm/parse
jvm.clj:	424	clojure.tools.analyzer.jvm/parse
analyzer.clj:	271	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	300	clojure.tools.analyzer/parse-if
analyzer.clj:	292	clojure.tools.analyzer/parse-if
analyzer.clj:	809	clojure.tools.analyzer/-parse
analyzer.clj:	805	clojure.tools.analyzer/-parse
jvm.clj:	427	clojure.tools.analyzer.jvm/parse
jvm.clj:	424	clojure.tools.analyzer.jvm/parse
analyzer.clj:	271	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	503	clojure.tools.analyzer/analyze-let
analyzer.clj:	490	clojure.tools.analyzer/analyze-let
analyzer.clj:	528	clojure.tools.analyzer/parse-let*
analyzer.clj:	523	clojure.tools.analyzer/parse-let*
analyzer.clj:	809	clojure.tools.analyzer/-parse
analyzer.clj:	805	clojure.tools.analyzer/-parse
jvm.clj:	427	clojure.tools.analyzer.jvm/parse
jvm.clj:	424	clojure.tools.analyzer.jvm/parse
analyzer.clj:	271	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	127	clojure.tools.analyzer/analyze-in-env/fn--4416
core.clj:	6979	clojure.core/mapv/fn--8535
protocols.clj:	168	clojure.core.protocols/fn--8249
protocols.clj:	124	clojure.core.protocols/fn--8249
protocols.clj:	19	clojure.core.protocols/fn--8204/G--8199--8213
protocols.clj:	31	clojure.core.protocols/seq-reduce
protocols.clj:	75	clojure.core.protocols/fn--8234
protocols.clj:	75	clojure.core.protocols/fn--8234
protocols.clj:	13	clojure.core.protocols/fn--8178/G--8173--8191
core.clj:	6886	clojure.core/reduce
core.clj:	6970	clojure.core/mapv
core.clj:	6970	clojure.core/mapv
analyzer.clj:	781	clojure.tools.analyzer/parse-invoke
analyzer.clj:	777	clojure.tools.analyzer/parse-invoke
analyzer.clj:	809	clojure.tools.analyzer/-parse
analyzer.clj:	805	clojure.tools.analyzer/-parse
jvm.clj:	427	clojure.tools.analyzer.jvm/parse
jvm.clj:	424	clojure.tools.analyzer.jvm/parse
analyzer.clj:	271	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	127	clojure.tools.analyzer/analyze-in-env/fn--4416
core.clj:	6979	clojure.core/mapv/fn--8535
PersistentVector.java:	343	clojure.lang.PersistentVector
core.clj:	6885	clojure.core/reduce
core.clj:	6970	clojure.core/mapv
core.clj:	6970	clojure.core/mapv
analyzer.clj:	183	clojure.tools.analyzer/analyze-vector
analyzer.clj:	180	clojure.tools.analyzer/analyze-vector
analyzer.clj:	50	clojure.tools.analyzer/eval4369/fn--4370
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	127	clojure.tools.analyzer/analyze-in-env/fn--4416
core.clj:	6979	clojure.core/mapv/fn--8535
protocols.clj:	168	clojure.core.protocols/fn--8249
protocols.clj:	124	clojure.core.protocols/fn--8249
protocols.clj:	19	clojure.core.protocols/fn--8204/G--8199--8213
protocols.clj:	31	clojure.core.protocols/seq-reduce
protocols.clj:	75	clojure.core.protocols/fn--8234
protocols.clj:	75	clojure.core.protocols/fn--8234
protocols.clj:	13	clojure.core.protocols/fn--8178/G--8173--8191
core.clj:	6886	clojure.core/reduce
core.clj:	6970	clojure.core/mapv
core.clj:	6970	clojure.core/mapv
analyzer.clj:	781	clojure.tools.analyzer/parse-invoke
analyzer.clj:	777	clojure.tools.analyzer/parse-invoke
analyzer.clj:	809	clojure.tools.analyzer/-parse
analyzer.clj:	805	clojure.tools.analyzer/-parse
jvm.clj:	427	clojure.tools.analyzer.jvm/parse
jvm.clj:	424	clojure.tools.analyzer.jvm/parse
analyzer.clj:	271	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	127	clojure.tools.analyzer/analyze-in-env/fn--4416
core.clj:	6979	clojure.core/mapv/fn--8535
PersistentVector.java:	343	clojure.lang.PersistentVector
core.clj:	6885	clojure.core/reduce
core.clj:	6970	clojure.core/mapv
core.clj:	6970	clojure.core/mapv
analyzer.clj:	183	clojure.tools.analyzer/analyze-vector
analyzer.clj:	180	clojure.tools.analyzer/analyze-vector
analyzer.clj:	50	clojure.tools.analyzer/eval4369/fn--4370
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	127	clojure.tools.analyzer/analyze-in-env/fn--4416
core.clj:	6979	clojure.core/mapv/fn--8535
protocols.clj:	168	clojure.core.protocols/fn--8249
protocols.clj:	124	clojure.core.protocols/fn--8249
protocols.clj:	19	clojure.core.protocols/fn--8204/G--8199--8213
protocols.clj:	31	clojure.core.protocols/seq-reduce
protocols.clj:	75	clojure.core.protocols/fn--8234
protocols.clj:	75	clojure.core.protocols/fn--8234
protocols.clj:	13	clojure.core.protocols/fn--8178/G--8173--8191
core.clj:	6886	clojure.core/reduce
core.clj:	6970	clojure.core/mapv
core.clj:	6970	clojure.core/mapv
analyzer.clj:	781	clojure.tools.analyzer/parse-invoke
analyzer.clj:	777	clojure.tools.analyzer/parse-invoke
analyzer.clj:	809	clojure.tools.analyzer/-parse
analyzer.clj:	805	clojure.tools.analyzer/-parse
jvm.clj:	427	clojure.tools.analyzer.jvm/parse
jvm.clj:	424	clojure.tools.analyzer.jvm/parse
analyzer.clj:	271	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	503	clojure.tools.analyzer/analyze-let
analyzer.clj:	490	clojure.tools.analyzer/analyze-let
analyzer.clj:	528	clojure.tools.analyzer/parse-let*
analyzer.clj:	523	clojure.tools.analyzer/parse-let*
analyzer.clj:	809	clojure.tools.analyzer/-parse
analyzer.clj:	805	clojure.tools.analyzer/-parse
jvm.clj:	427	clojure.tools.analyzer.jvm/parse
jvm.clj:	424	clojure.tools.analyzer.jvm/parse
analyzer.clj:	271	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	272	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	300	clojure.tools.analyzer/parse-if
analyzer.clj:	292	clojure.tools.analyzer/parse-if
analyzer.clj:	809	clojure.tools.analyzer/-parse
analyzer.clj:	805	clojure.tools.analyzer/-parse
jvm.clj:	427	clojure.tools.analyzer.jvm/parse
jvm.clj:	424	clojure.tools.analyzer.jvm/parse
analyzer.clj:	271	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	503	clojure.tools.analyzer/analyze-let
analyzer.clj:	490	clojure.tools.analyzer/analyze-let
analyzer.clj:	528	clojure.tools.analyzer/parse-let*
analyzer.clj:	523	clojure.tools.analyzer/parse-let*
analyzer.clj:	809	clojure.tools.analyzer/-parse
analyzer.clj:	805	clojure.tools.analyzer/-parse
jvm.clj:	427	clojure.tools.analyzer.jvm/parse
jvm.clj:	424	clojure.tools.analyzer.jvm/parse
analyzer.clj:	271	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	272	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	272	clojure.tools.analyzer/analyze-seq
analyzer.clj:	262	clojure.tools.analyzer/analyze-seq
analyzer.clj:	63	clojure.tools.analyzer/eval4381/fn--4382
MultiFn.java:	234	clojure.lang.MultiFn
analyzer.clj:	284	clojure.tools.analyzer/parse-do
analyzer.clj:	276	clojure.tools.analyzer/parse-do
...

chromalchemy15:03:14

Seems to be something with naming specter paths with def or defn and using them in wrapper fns.

(defn mypath [x] (path ALL x))

(select (mypath MAP-VALS) data)
Everything compiles in repl fine. But I haven’t locked down a reproduction yet. Will have to postpone til later.

Aziz Aldawood16:03:04

does clerk work in babashka project? I am assuming the answer is no

borkdude16:03:02

@U02Q6L92FEJ Here is a very light-weight way to get a notebook like thing in babashka: https://www.loop-code-recur.io/live-clojure-cookbooks/ It's not clerk, but it's something. The above PR would be better though

👍 2
Aziz Aldawood16:03:56

is this still of interest? I use babashka to do ad hoc analysis and it would be nice to visualise and potentially publish some of the results within the company. having plotly, html, vega lite blocks is really convenient

4
borkdude16:03:08

I think it would be really cool :)

borkdude17:03:13

@U5H74UNSF btw, tools.analyzer is now also almost compatible with babashka, more so than a while ago (but maybe too much for bb clerk)

Andrea17:03:10

I should refresh that PR

mkvlr20:03:56

something without the analysis and incremental computation would probably be reasonable for the bb variant, if you want incremental computation I think it’s better to use the normal clerk

mkvlr20:03:42

clerk’s viewers could also run all in the browser in a custom scittle build for example

borkdude20:03:47

doesn't clerk's bundle already run and come with SCI? why would it not work as is with bb-clerk?

mkvlr20:03:16

yeah it does

borkdude20:03:51

@U5H74UNSF the analysis and incremental computation could also be less sophisticated, e.g. only look at the superficial forms

mkvlr20:03:06

or skip it completely?

mkvlr20:03:25

just run the whole script

borkdude20:03:36

I think it could still be useful, e.g. when you're making http requests etc to not have to do them over and over (rate-limiting etc)

mkvlr20:03:41

since there’s no nippy in bb I don’t think a cache is worth the effort

borkdude20:03:29

why is cache bound to exclusively one specific format? could also be transit maybe

mkvlr20:03:57

could be, of course

mkvlr20:03:50

so far the PR has gotten no interested except for four hearts so unsure there’s a strong user demand

borkdude20:03:22

@U02Q6L92FEJ: maybe add a heart ;)

mkvlr20:03:23

main open questions would be how to run the markdown parser and the file watcher

borkdude20:03:00

file watcher is solvable in bb using the pod. but also: most people don't use the file watcher probably and we could do without

mkvlr20:03:26

I think most people do use it

borkdude20:03:45

don't you mostly recommend doing the emacs M-. thing?

mkvlr20:03:47

but don’t have numbers

mkvlr20:03:09

yes, but not sure folks listen

mkvlr20:03:30

the file watcher is the more beginner friendly thing for sure

borkdude20:03:48

I don't like side effects happening when I'm typing

borkdude20:03:11

and without caching that will be even more unpleasant

mkvlr20:03:17

I don’t love it either

mkvlr20:03:53

could also just be the in-memory cache and no on-disk cache

mkvlr20:03:26

but for the cache to properly work we need tools analyzer again…

borkdude20:03:57

and that's why I suggested a less sophisticated form of caching: just based on the hash of the superficial s-expression

borkdude20:03:26

and 1-file based, ignoring all dependencies. just run clerk/clear-cache when you change a dep

mkvlr20:03:32

maybe a first goal could be a really simple thing where bb is mainly being used to serve a local file to the browser

borkdude20:03:36

(for bb I mean, not in general)

mkvlr20:03:38

@U9EQP1K0X maybe we should submit a talk to bb conf 🙃

borkdude20:03:40

or not even hash of s-expression: just the s-expression itself, since you can compare by value

mkvlr20:03:02

then we have the most important thing: a deadline

borkdude20:03:16

I think only the last line in that patch is currently needed

borkdude20:03:20

@U5H74UNSF your clerk + bb talk could be a talk about clerk, but also about how to make a library bb-compatible, experience report ;)

hlship16:03:01

What is the right way to render an image inside some Hiccup code? I'm trying to render a chess board?

(ns local.chess-board
  "Renders a chess board from a chess position map."
  (:require [ :as io]
            [nextjournal.clerk :as clerk])
  (:import (javax.imageio ImageIO)))

(defn- read-asset [asset-name]
  (ImageIO/read (io/file "assets" (str "chess_" (name asset-name) ".png"))))

(def knight (read-asset :knight))

(clerk/html
  [:div.bg-black
   knight])

hlship16:03:34

I've tried some variations of introducing an :img tag, but that hasn't worked.

mkvlr16:03:41

in the latest clerk clerk/image should just work inside hiccup

hlship16:03:49

Thanks!

😀 2
hlship22:03:48

I'm having a bit of trouble composing viewers. I have my 4x4 chess board, and code to output it as HTML: https://gist.github.com/hlship/ad554ccfb7bb39e7cce16afb65302b4c And when I create a new board, this seems to work:

hlship22:03:41

But, I want to be able to output HTML that includes the board output. I seem to be hanging browser with:

(clerk/html
  (b/render-board (new-board 1 1 :pawn))
  (b/render-board (new-board 2 2 :queen)))
I was hoping that there was an automatical way that I can use clerk/html to produce some labels intermixed with HTML output for boards (I want to show a sequence of moves and how the board changes with each one).

Andrea11:03:12

nice, yeah, clerk/html does a bit of manipulations on the JVM, prior to sending hiccup data to the client... maybe you could try a sync atom (https://github.com/nextjournal/clerk/blob/9fa6b3761fec5544bacdf4117d0ec9b87b411a40/notebooks/viewers/control_lab.clj#L36-L45 ) which holds board data on the JVM side and have a :render-fn that takes care of representing your data in the client when the “board model” change (see the link above). I guess the problem here is how to manage the images for the pieces... maybe an SVG format could be more amenable.

Andrea12:03:06

@U04VDKC4G thanks for reporting the issue, I think nested calls to clerk/html don’t work at present. As you figured out, a single outer call against a hiccup vector with interspersed clerk viewers inside it does work.

mkvlr13:03:26

@U9EQP1K0X guess we could drop the nested html calls?

Andrea13:03:31

yeah, drop as in act as identity

👍 4
hlship23:03:30

The closest I have is:

(clerk/html
  [:<>
   (b/render-board* (new-board 1 1 :pawn))
   [:div "Then:"]
   (b/render-board* (new-board 3 2 :queen))])