hyperfiddle

2025-05-01T16:22:34.216029Z

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/On

xificurC 2025-05-02T07:35:22.315959Z

We 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))

👍 1
❤️ 1
🙏 2
Dustin Getz (Hyperfiddle) 2025-05-02T11:04:45.555959Z

@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)

🙏 1
Dustin Getz (Hyperfiddle) 2025-05-02T11:06:47.929419Z

here is a temporary archive of the old amb tutorial: https://docs.google.com/document/d/1WaT-8_w6qGC0BKAdkGaF6uTs6cCRS0qA4UM9Q9HITVk/edit?tab=t.0

1
🙏 1
2025-05-03T03:27:54.362919Z

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

Dustin Getz (Hyperfiddle) 2025-05-01T16:47:45.490929Z

missing initial value to dom/On

🙏 1
2025-05-01T23:33:46.975099Z

is there a list of major changes from v2->v3? e.g. did e/fn invocation change? we no longer use new?

noonian 2025-05-02T00:28:34.204129Z

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

🙏 1
2025-05-02T04:54:40.142219Z

is there a definitive explainer for amb and diff-by?

2025-05-02T06:26:44.528089Z

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?

2025-05-03T14:10:35.380239Z

i'm seeing if amb works for my use case

Dustin Getz (Hyperfiddle) 2025-05-03T14:14:45.842329Z

how big is your map

Dustin Getz (Hyperfiddle) 2025-05-03T14:14:50.506669Z

how fast does it update

2025-05-03T14:19:30.119359Z

this use case is very small. it's a map of tens of elements, and updates with user actions (so very slowly)

2025-05-03T14:19:53.053009Z

but i'd also like to learn what a more general solution might look like

2025-05-03T14:20:04.365429Z

like in rama, you'd use paths to subscribe to a view over a data structure

Dustin Getz (Hyperfiddle) 2025-05-03T14:20:20.032969Z

so you're facing a performance issue? Please elaborate about that

2025-05-03T14:21:46.646169Z

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

2025-05-03T14:22:26.556069Z

current project isn't big enough to see any issues, but i'm still using the global state strategy

2025-05-03T14:23:36.351949Z

maybe i'll check back in if i hit latency

Dustin Getz (Hyperfiddle) 2025-05-03T14:24:10.292399Z

oh i misunderstood, you are not facing a perf issue, your map updates slowly, you are interested in the abstract considerations?

2025-05-03T14:25:01.603629Z

yep

Dustin Getz (Hyperfiddle) 2025-05-03T14:26:21.582639Z

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.

Dustin Getz (Hyperfiddle) 2025-05-03T14:26:22.292849Z

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

👍 1
Dustin Getz (Hyperfiddle) 2025-05-03T14:28:11.150759Z

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

2025-05-03T14:32:46.057029Z

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.

2025-05-03T14:33:12.356699Z

since the current project is small, i'm not hitting any ceiling yet. but i have a feeling my current approach won't scale

2025-05-03T14:34:33.407749Z

in the meantime, if we happen to have any reference apps showing these patterns implemented well, i'd love to take a look

Dustin Getz (Hyperfiddle) 2025-05-03T14:35:49.791969Z

"use local state" i think doesn't require explanation, start there

👍 1
Dustin Getz (Hyperfiddle) 2025-05-03T14:36:14.837679Z

"optimize by hand" is entangled with your data architecture, it will be different for each project

Dustin Getz (Hyperfiddle) 2025-05-03T14:37:04.755769Z

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

👀 1