Fork me on GitHub
#hyperfiddle
<
2023-07-10
>
grounded_sage04:07:05

I am using the GPT api and electric seems to crash sometimes. I am assuming it is a timing out issue?

Geoffrey Gaillard10:07:27

Could you share more info to help us diagnose the issue? What does the crash looks like?

Dustin Getz17:07:09

@U05095F2K can you add me to your repo i want to check your entrypoint

👍 2
Hendrik06:07:55

I have general question when things are executed in an e/defn . I have prepared this example

(e/defn my-fn [x y]
  (println "executed once when my-fn is mounted into the dag")
  (println "executed on mount and whenever x changes" x)
  (println "executed on mount and whenever y changes" y)
  (e/on-unmount #(println "executed once on unmount"))
  (e/on-unmount #(println "executed once on unmount, too")))
Are these statements correct? If so, is there an order in which things are executed. I read that everything is async. E.g:
(e/defn my-fn [x]
  (println "must run and finish first" x)
  (println "must run after above has finished" x))
Is the second example true? If not, how can I enforce this?

braai engineer09:07:23

You could probably wrap them in a (do ...) statement but then both will run each time.

2
Hendrik09:07:01

Electric (do ...) does execute statements synchronous in order? That would be fine for the second example because both statements rely on the same value

braai engineer09:07:18

Could also move the println's out to a Clojure function:

(defn do-both [x y]
  (println "executed on mount and whenever x changes" x)
  (println "executed on mount and whenever y changes" y))

(e/defn my-fn [x y]
  (do-both x y))

Geoffrey Gaillard10:07:34

In Electric Clojure (do …) executes statements in parallel. I understand you want to sequence some effects. What’s your use case?

braai engineer11:07:33

Oh wow, I did not expect that. What about (e/server (do ...))? What happens with a doseq? This might explain why I had weird state related stuff happening with checkbox some weeks ago... How do you force something to run in sequence that does not have symbolic dependency, m/sp? e.g.

(do
  (d/transact conn ...)
  (d/q ... (d/db conn))

Hendrik12:07:32

@U2DART3HA I am trying to wrap three.js webgl library. I think that electrics RAII features are a perfect fit for cleaning up GPU resources. Some example usecase:

;e.g render after resize. pseudo code
[width height] (stream-of-resize-events)
(.setSize renderer width height)
(.render renderer scene)

👍 2
Geoffrey Gaillard12:07:54

Three.js API is essentially imperative whereas Electric Clojure is functional and reactive. Today's short answer is to move your imperative code into clojure land, so it follows Clojure semantics.

;; Anonymous fn reruns on each !my-atom change
((fn [value] (prn "first") (prn "second")) (e/watch !my-atom))

Geoffrey Gaillard12:07:25

@U051SPP9Z you'd watch the datomic tx log and recompute (d/db conn) which in turn recomputes (d/q db). If you really need to perform d/q just after transact, then move the code to a Clojure function. doseq behaves as in Clojure. for also. They evaluates sequentially. e/for evaluates in parallel.

braai engineer12:07:37

Understand that e/for is parallel, but to confirm in (do (a) (b)), a and b will execute in parallel? I’m guessing in two separate threads?

Geoffrey Gaillard12:07:31

Yes (a) and (b) will execute in parallel. But in the same thread (JavaScript is usu single threaded). If you want to run (a) or (b) on a different thread (JVM only) have a look at e/offload.

Hendrik12:07:11

isn’t running in the same thread in JS Land synchronous? Or is the order which runs first not determined?

Geoffrey Gaillard12:07:37

The order of which runs first after the initial run is not determined. Because Electric compiles you program to a graph and maintains it. Let me clarify:

(def !my-atom (atom 0))
(e/def my-value (e/watch !my-atom))

(e/run (do (prn "No dependency")
           (prn "One dependency:" my-value)
           (prn "Again one dependency: " (inc my-value))))
This program will print immediately:
"No deppendency"
"One dependency 0"
"Again one dependency 1"
These lines will print in order because it’s the first run. The program says up! So when you (swap! !my-atom inc) only what depends on !my-atom is recomputed:
"One dependency 1"
"Again one dependency 2"
The order of these two lines is not guaranteed.

👍 2
Hendrik13:07:49

thanks for the clarification

Dustin Getz15:07:12

What does "parallel" mean? Electric is a massively concurrent DAG language, which means we automatically maximize concurrency at every point. (to the extent that we can without breaking Clojure compatibility) (do (println x) (println y)) has ambiguous order, because x or y can be remote, in which case there may be latency when the expression "boots" (runs for the first time). y can be available first if x is remote. In subsequent reactive changes to x or y, Electric will maximize concurrency and recompute only the expression whose arguments changed. If both reactive values are available when the do expr boots, the println exprs will compute (run) in lexical (natural) order. (do (println 1) (println 2)) will print 1 2 in that order (literals are never remote), and then never print again (literals never update). --- (dom/div (do (dom/text x) (dom/text y))) will perform point writes in ambiguous order (like println), but with an additional rule: the DOM has a notion of order — an element's children are ordered — so we will ensure that the final result is <div>{$x}{$y}/> even if y is written first. Note the do is implicit, I phrased it explicitly for clarity. On lexical ordering in the Electric DAG: a DAG node's list of edges are lexically ordered. Electric provides an undocumented api e/hook to implement resource effects that have a notion of positional order and lifecycle, like dom elements. println and other side effects do not have such a notion of positional ordering and therefore do not opt into that API. --- (let [] (dom/text x) (dom/text y)) – same as above because of implicit do. Thus similarly all other clojure forms that imply do such as when e/defn (do (dom/div (dom/text x)) (dom/div (dom/text y))) will behave as per the above rules, but we must realize that div is in fact a macro, so the evaluation order has been mucked with to get declarative syntax (i.e., the div element must be first created so that the text element can then be attached). This is best understood by reading the source code of dom/element and dom/text which are about 10 lines total. @U053XQP4S @U2DART3HA please confirm I have not made any errors, we also may need to tighten some vocabulary

👍 4
Dustin Getz15:07:02

Specifically we need to clarify our usage of "parallel" vs "concurrent" i think

Hendrik15:07:43

Dustin thank you for your detailed explanation. Now, I have a much better understand on how electric is working 🙂

🙂 2
braai engineer13:07:19

@U09K620SG is it a good idea to use the same semantic "do" for a process with no guaranteed execution order? Maybe ado or pdo ? Just like Clojure has pmap (which is lazy tho). This has tripped me up and will probably trip others up too.

Dustin Getz13:07:01

yes, it is this way because it cannot reasonably be any other way, this was hard fought knowledge

Dustin Getz13:07:33

To be clear: electric's implementation of (do) is fully backwards compatible with clojure; if you copy paste pre-existing Clojure code into Electric (or call a pre-existing Clojure macro), the electrified expression will return the exact same result as it would in clojure

Dustin Getz13:07:04

including running the effects in the exact same order, as demonstrated by (do (println 1) (println 2))

leonoel15:07:50

> fully backwards compatible with clojure almost. (do (assert false) (println 1)) doesn't print in clojure but prints in electric

🙏 1
Dustin Getz16:07:16

Recalling now Leo's correction, let us consider:

(def !x (atom true)) 
(e/run (do (dom/text "a") (assert (e/watch !x) "b") (dom/text "c")))
(swap! !x not)
for clarity, e/run marks an electric reactive expression as if in a unit test Should text element "c" be destroyed and removed from the DOM when !x toggles? What about "a"? What happens when !x toggles back?

braai engineer08:07:15

Is (e/server (do-thing) nil) equivalent to (e/server (e/discard (do-thing)))?

braai engineer09:07:24

So excited for incremental compilation

braai engineer09:07:05

Which cache does HYPERFIDDLE_ELECTRIC_SERVER_VERSION bust? Could server not set E-Tag on index.html and/or the JS it serves? On connect check version and if server & client don't match, force client reload?

Geoffrey Gaillard10:07:13

HYPERFIDDLE_ELECTRIC_SERVER_VERSION will trigger a client page refresh if it is set and doesn’t match HYPERFIDDLE_ELECTRIC_CLIENT_VERSION (hardcoded at build time) About ETAGs. Websocket client has auto-reconnect. If a new server version is deployed (e.g. v1 to v2), the websocket client (v1) will be disconnected and will reconnect to sever (v2). At this point there’s no HTTP request loading HTML or JS. Only the Websocket Upgrade. So no ETAG if I’m not mistaken.

braai engineer11:07:50

If HYPERFIDDLE_ELECTRIC_SERVER_VERSION is hardcoded at build time why does it need to be passed in when running uberjar? https://github.com/hyperfiddle/electric-starter-app#deployment Anything that tries to start the JAR needs to know something about its version which does not make sense to me, even though it can't change the version. Makes it hard to deploy via Dokku in Procfile.

Dustin Getz15:07:39

i believe this because today the dag is actually baked into the client, the server is general

Dustin Getz15:07:43

perhaps the flag can be removed with minor upgrades to build.clj in the starter app

braai engineer11:07:16

How would the Datomic tx listener change to support separate Datomic databases per customer, but still stored in the same Datomic System and one app deployed? I'd have to establish a separate conn per customer when they log in and hold onto it somewhere. I imagine I would have to move !db & !taker into a map per authenticated username / tenant ID? Could I use the !present pattern but instead of usernames, put Datomic connections or DBs in there? How do I create session-specific atoms? I would like to avoid watching a singleton atom with everyone's DB values that triggers a refresh for all customers any time anyone transacts anywhere.

Dustin Getz12:07:21

(def !x (atom .)) ; global state (let [!x (atom .)) ; local state

braai engineer13:07:39

By session-specific I mean auth-scoped so that a Datomic connection can be shared between two user windows, which I guess will mean having a global conns map keyed on user ID => conn and then subscribing/unsubscribing from txReportQueue. I recall there being a limit, i.e. only a single txReportQueue per...connection? Or are Datomic connections relatively cheap? I imagine that each connection acts like own peer with own cache? So to share datom cache you'd probably want to share conn between Electric sessions with same tenant / user ID?

Dustin Getz14:07:14

These are expert level Datomic questions, i'd ask in their support channel

mgxm14:07:49

I'ts possible to dynamic call and dispose an electric component? • Something like that: ◦ 1) Client receives an string representing a component, ex: "ComponentA" ◦ 2) Client looks into registry for "ComponentA" ◦ 3) Client call new on that Component. ◦ 4) Client can dispose (unmount) this component in other parts of the code.

Dustin Getz15:07:00

mount/unmount are implicit, userland cannot explicitly dispose

Dustin Getz15:07:32

Show me some pseudo-code for what business problem you are trying to accomplish

Dustin Getz15:07:39

in https://electric.hyperfiddle.net/user.tutorial-lifecycle!Lifecycle note that the blinker is added/removed to the DOM but there is not an explicit disposal, you can also see in the console that we've hooked the mount/unmount lifecycle

mgxm16:07:50

I'm thinking of way to integrate with https://golden-layout.com/. • There are two ways to integrate. ◦ 1) Register components in their own way. ▪︎ the lib will mount/unmount the component in its own way. ◦ 2) Register two callbacks, bindComponent and unbindComponent ▪︎ here, it's up to you to mount/unmount the component on the dom

mgxm16:07:57

pseudo-code for the idea:

(GoldenLayout. dom/node
  (fn bind
    [container item]
    (let [name     (.-componentName item)
          el       (.-rootElement container)
          resolved (get registry name)]
      (dom/with el
        (new resolved))))
  (fn unbind
    [container]
    (let [el (.-rootElement container)]
      (dispose el))))

Dustin Getz17:07:49

Electric component inside GoldenLayout element?

Dustin Getz17:07:39

this is a POC of a fulcro adapter, though in this configuration each instance of the with-electric bridge will get it's own websocket to the server (which works fine)

mgxm17:07:52

great.. thanks

J15:07:58

Hi! Is there emacs users here (with clojure-lsp)? What is your hack to disable Unresolved namespace :

(ns app
  #?(:clj [app.db :as db]))
 
(e/server (db/foo ...)) ;; Unresolved namespace `db
`

braai engineer11:07:50

If HYPERFIDDLE_ELECTRIC_SERVER_VERSION is hardcoded at build time why does it need to be passed in when running uberjar? https://github.com/hyperfiddle/electric-starter-app#deployment Anything that tries to start the JAR needs to know something about its version which does not make sense to me, even though it can't change the version. Makes it hard to deploy via Dokku in Procfile.