This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-04-30
Channels
- # asami (4)
- # babashka (3)
- # beginners (21)
- # biff (22)
- # cljs-dev (6)
- # clojure (11)
- # clojure-europe (3)
- # clojure-norway (35)
- # clojure-spain (4)
- # clojurescript (14)
- # datalevin (2)
- # emacs (12)
- # exercism (2)
- # fulcro (10)
- # honeysql (4)
- # humbleui (3)
- # hyperfiddle (49)
- # instaparse (1)
- # introduce-yourself (1)
- # missionary (1)
- # off-topic (109)
- # pathom (2)
- # practicalli (21)
- # random (2)
- # rdf (2)
- # releases (2)
- # scittle (3)
- # specter (2)
Hello again, I'm looking to convert a self-ref map that represents a state graph to a DAG structure that electric will be able to consume easily, I know you've worked with something like this under the hood, except for the self-ref part but that's on me, I'm just looking for some target representation of a DAG, can you recommend anything?
tell me more about the problem you’re solving so i can understand the use case?
We have nested structures that looks like this:
{:name ""
:greeting (str "Hello, " (this :name))}
*this is a simple example of course.
It can be compiled to something like this:
(let [!name (atom "")
name (e/watch !name)
greeting (str "Hello, " name)]
...)
This will require us to do ourselves the traversals and manage the dependencies.
So I was wondering if you know and data-structure in clojure that can handle this kind of self-ref or at least a good target to compile our data to it and from there to electrici need to think about it. we have pretty much this exact structure inside HFQL, we analyze it to datascript and then query datascript to emit electric code
perhaps HFQL itself solves your business problem, if you zoom out a level or two what are we trying to accomplish with these maps?
it's a bit high level, and out of context it might lose its meaning, but I guess it comes down to expressive and reusable state management
this aspect (self-ref) resembles mobx a bit
datascript was one of my first hunches too, I started experimenting with it.
like are you rendering forms, or what
My POC is a simple form, yes:
(e/defn Input-example []
(e/client
(let [!name (atom "")
name (e/watch !name)
greeting (str "Hello, " name)]
(dom/input (dom/on "input" (e/fn [e] (swap! !name #(-> e .-target .-value))))
(dom/props {:value name}))
(dom/p (dom/text greeting)))))
This is my first target from the state in the example aboveHFQL may be the exact thing you need, we haven’t published it yet but there are some screenshots here https://hyperfiddle.notion.site/Hyperfiddle-progress-update-Feb-2023-8cc45f9da47c4719bb16851d129e3a3d
I think HFQL is too high level, I'm trying to handle only state (abstract data) for now. How did you break arbitrary maps into datascript? if I'm not specifying a ref in the schema it just takes the maps as a whole value. is there a way to tell datascript to break arbitrary maps to EAVs?
Roughly speaking HFQL extends EQL. It doesn't try to manage state but express a view
In the example you gave
{:name ""
:greeting (str "Hello, " (this :name))}
IMO the "map" here seems to be the desired result {:name "Lidor" :greeting "Hello, Lidor"}
, which is a projection/query over an underlying graphI hear that you think HFQL is too high level, but if I may show you one more thing
This is how I'd produce basically the above map using HFQL and a datascript graph:
(defn greeting [name_]
(str "Hello, " name_))
(e/defn Form [id]
(hf/hfql
[:person/name
(greeting person/name)]
id))
(comment
(Form. 1) :=
{:person/name "Alice"
'(greeting person/name) "Hello, Alice"})
you can process it from there, we have an :alias directive as well if you need the key to be :greeting and not '(greeting id)
That looks very much like the target I had in mind for that code, I still need to workout the details and how to work with datascript
But basically: Traverse graph Primitives turn to values in ds Computations turn to functions with pointers to dependants
edited the gist to be simpler and slightly more powerful with a sideways lookup
(defn greeting [id]
(str "Hello, " (d/entity db id)))
(e/defn Form [id]
(hf/hfql
[:person/name
(greeting id)]
id))
(comment
(Form. 1) :=
{:person/name "Alice"
'(greeting id) "Hello, Alice"})
This one?yeah i edited that, it uses ‘(greeting person/name) now
It seems like the value of electric-lang vars is nil
is clojure-land.
(e/defn E [])
(def m {"k" E})
(println m)
Is this expected?This came up as I was trying to invoke an Electric function from a Plotly event callback. Very roughly, the callback code looks like
(fn [e] (new ElectricFn e))
Since this code lives in clojure-land, ElectricFn is nil
and, thus, the code failsWell, I’m a bit of a dummy. I just noticed the clojurescript compiler complaining {k #object[clojure.lang.Var$Unbound 0x63bc661f Unbound: #'app.plotly-interop/E]}
this is internals and could change. today, e/def quotes the body and saves it as metadata for a later compiler phase. electric things are only visible from inside/under the electric entrypoint
in (fn [] (new ElectricFn)), new is clojure new not electric new because it’s in a clojure context
For interop reasons, I think it would be helpful to have access to the Electric new
post the whole plotly integration pls, we will find a clean way to bridge them
My strategy to send plotly events into electric-land is by dispatching events to the surrounding div where I’ve added dom/on
reactions
if you look at our codemirror integration in the repo you might be able to figure out the pattern, the idea is to bridge the event to a discrete missionary flow immediately and then adapt to electric from there
cool. Can you point me to that? I don’t remember seeing a codemirror integration in the tutorials
on mobile you’ll need to grep the main repo
no worries. thanks!
this constructs a codemirror editor, registers a value change listener, and redirects change events into a missionary discrete flow >cm-v
with m/observe. >cm-v which is then parsed (e.g. string to edn) and given an initial value before bridging to electric with new
. https://github.com/hyperfiddle/electric/blob/faab6602ee84361d81f24e31609f01b5748a3d6f/src/contrib/electric_codemirror.cljc#L73-L83
m/observe is the key primitive that adapts an event listener callback interface to a discrete flow, note m/observe also forces you to define the cleanup code in the same place to unregister the callback and destroy the codemirror (which missionary will call during teardown)
I stared at this code for a while and I have low confidence I understand about 30% and high confidence that I don’t understand 70% of it 🙂 My current approach is working: reflect events onto a div
on which I can place an electric on
and use e/fn
s from there.
Is there an issue for improving the ergonomics of javascript->electric interop?
I think the real issue here is that the codemirror API is more complex than it seems which is causing contorted code to functionalize it
Based on my understanding of the plotly API (which is redirecting events through the div it attaches to) i think this is sufficient:
(e/defn Plotly [plotly-config]
(let [plotly-id (str (random-uuid))]
(dom/div (dom/props {:id plotly-id})
(plotly/newPlot plotly-id (clj->js plotly-config))
(dom/on! "plotly_click" (fn [data]
(let [curve-number ^int (. (aget (. data -points) 0) -curveNumber)
point-number ^int (. (aget (. data -points) 0) -pointNumber)]
[curve-number point-number]))))))
(comment
(let [plotly-click-data (Plotly. plotly-config)]
; electric body
...))
We could tighten it up by using m/observe to separate the plotly lifecycle from the div's lifecycle (allowing you to remove the plotly but keep the div) but it doesn't seem to matter here
As to making this easier to figure out, its unclear what's possible, i need to see a lot more of these and maybe there is some sort of common pattern, or also possibly each JS integration is a unique snowflake. This guy was a bit of an oddball as well
Your code snippet doesn’t work because (I believe) of some plotly wierdness. From what I can tell, plotly events are not real javascript events. Plotly attaches an on
function to its div
and the client must call that to attach listeners. Making it more complicated, plotly/newPlot
returns a JS promise which, on resolution, returns the div
to which its on
function is attached.
thus, my entire Plotly-Chart electric function looks like:
(e/defn Plotly-Chart [plotly-config event-Reactions]
(e/client
(let [id (str (random-uuid))]
(dom/div
(doseq [[event Reaction!] event-Reactions]
(dom/on event Reaction!))
(dom/props {:id id}))
(.then (plotly/newPlot id (clj->js plotly-config))
(fn [div]
(doseq [[event _] event-Reactions]
(.on div event
(fn [data]
(let [event (new js/Event event)
curve-number ^int (. (aget (. data -points) 0) -curveNumber)
point-number ^int (. (aget (. data -points) 0) -pointNumber)]
(set! (.-data event) [plotly-config curve-number point-number])
(.dispatchEvent div event))))))))))
ok i’ll look closer at that .on synthetic thing. we already have the div, do we need to wait again?
> do we need to wait again?
Not sure what you mean by this.
Right now, I’m reflecting the plotly data
(available from its weird callback) as a real javascript event to the electric div
. My ideal would be to take the plotly data
object and pass it directly to a electric fn. Something like the following code:
(e/defn Plotly-Chart [plotly-config event-Reactions]
(e/client
(let [id (str (random-uuid))]
(dom/div
(dom/props {:id id}))
(.then (plotly/newPlot id (clj->js plotly-config))
(fn [div]
(doseq [[event Reaction] event-Reactions]
(.on div event
(fn [data]
(new Reaction data)))))))))
Obviously this doesn’t work because (as you pointed out earlier), the (new Reaction data)
is not an electric new
.
It occurs to me that a real implementation would also have to convert data
to edn
before creating the Reaction object because it might have to be sent over the wire.