what's wrong with this expression? (i'm new to dom3)
(e/defn FileDropzone []
(dom/div
(dom/props {:class "file-dropzone"})
(dom/input
(dom/props {:type "file"
:multiple true
:style {:display "none"}})
(dom/On "change" (fn [e]
;; process files
)))))
Error in phase :execution
Wrong number of args (4) passed to: hyperfiddle.electric-dom3/OnWe haven't internally settled on a good explainer for amb. Here's my short personal take on explaining the basics (80/20) with some simplifications.
Electric operates on differential collections (let's abbreviate DC). Everything, even code like 1 is compiled to a DC of size 1.
amb is close to a reactive concatenation function. Consider a reactive concat called CONCAT that you can call like (CONCAT (DC 1 2) (DC 3 4)), you get (DC 1 2 3 4). In electric everything is a DC, as we explained earlier even constants and literals lift to a DC of size 1. So (e/amb 1 2) is (CONCAT (DC 1) (DC 2)) -> (DC 1 2). But it's also fair to write (e/amb (e/amb 1 2) (e/amb 3 4)) to get (CONCAT (CONCAT (DC 1) (DC 2)) (CONCAT (DC 3) (DC 4))) -> (CONCAT (DC 1 2) (DC 3 4)) -> (DC 1 2 3 4). Since the following are all valid - (e/amb 1 2), (e/amb (e/amb 1) (e/amb 2)), (e/amb 1 (e/amb 2)), one can see it's tricky to name this operation concatenation, syntactically it doesn't make sense. But operationally that's what happens after literal lifting.
What can one do with a DC?
• operate on each entry with e/for. (e/for [x (e/amb 1 2)] (prn x))
• turn it back into a clojure collection with e/as-vec. (e/as-vec (e/amb 1 2) -> [1 2]
• use in a cross product (this is expert level). (prn 'value (e/amb 1 2)) is (APPLY (DC prn) (DC 'value) (DC 1 2)), which will run as a cross product, i.e. sort of like (prn 'value 1) and (prn 'value 2) concurrently. If more DCs have >1 size one gets a cross product. The temperature2 demo uses this pattern to call reset! over a DC of size 2. It could use e/for to get the same effect though.
e/diff-by is a simple operator that allows turning a clojure collection into a differential one. (e/for [x (e/diff-by identity [1 2 3])] (prn x))
@wei i took down the amb tutorial while we rewrite our docs because it was scaring people off and making them feel like electric v3 is more complicated than v2 (which it is NOT, this is a messaging issue)
here is a temporary archive of the old amb tutorial: https://docs.google.com/document/d/1WaT-8_w6qGC0BKAdkGaF6uTs6cCRS0qA4UM9Q9HITVk/edit?tab=t.0
are you able to to watch a portion of a data structure (e.g. a submap)? i have a global app-state atom (not sure if that's still a good idea) and i'm wondering what the performance considerations are for subscribing to changes on it
missing initial value to dom/On
is there a list of major changes from v2->v3? e.g. did e/fn invocation change? we no longer use new?
Just call it like a normal fn and make sure the name is capitalized. There is also e/call which I think is required for anonymous e/fn's
is there a definitive explainer for amb and diff-by?
I stared at the temperature2 example and I only sort of get amb. Maybe I need to understand m/ap first. Can I get a snippet demonstrating how to map over a client-side seq and gather values concurrently? In vanilla Clojure I might write something like (pmap expensive-server-side-fn s) - I think this is something you might use amb for in electric, right?
i'm seeing if amb works for my use case
how big is your map
how fast does it update
this use case is very small. it's a map of tens of elements, and updates with user actions (so very slowly)
but i'd also like to learn what a more general solution might look like
like in rama, you'd use paths to subscribe to a view over a data structure
so you're facing a performance issue? Please elaborate about that
i built an app in v2 with a top-level watch over global state that had some performance issues (~1s response times). i remember demoing that app to you at some point
current project isn't big enough to see any issues, but i'm still using the global state strategy
maybe i'll check back in if i hit latency
oh i misunderstood, you are not facing a perf issue, your map updates slowly, you are interested in the abstract considerations?
yep
the central problem here is that the whole point of a central state store, let's say a database, is to make transactions (global graph writes) and queries (global graph reads) easy. But the global nature of graphs means that if you change even a single edge or node, it can have global consequences, e.g. a chain of joins will resolve differently. That means all queries need to be checked with each transaction.
There are two ways to defeat this ceiling: • incrementalize the database (e.g., Materialize) • give up global state and use local state instead, now writes can only have local consequence
The third way (when you need global state and you start hitting performance issues) is to optimize by hand, you can use the cursor pattern to present a local projection as an IAtom, and make sure watchers are only notified for local changes
thanks for the explanation. gives me several ideas for how to optimize the v2 app if i were to revisit it. i'd probably try to materialize some views on the db first.
since the current project is small, i'm not hitting any ceiling yet. but i have a feeling my current approach won't scale
in the meantime, if we happen to have any reference apps showing these patterns implemented well, i'd love to take a look
"use local state" i think doesn't require explanation, start there
"optimize by hand" is entangled with your data architecture, it will be different for each project
there are clojurescript cursor libraries on github for the simple case of - atom has many keys and each key is a different topic and i want update granularity to be per topic