Fork me on GitHub
#hyperfiddle
<
2023-08-23
>
henrik06:08:54

Hopefully they’ll relent and give access to the Clojure API, because building a bespoke Clojure API around a Java API around a Clojure API is silly.

12
danielstockton10:08:28

I'm new to following this stuff. Can anyone tell me how Rama relates to electric? Is it another take on the idea of combining front and backend, or somehow complimentary?

danielstockton10:08:43

It looks like it's a paradigm similar to differential dataflow?

Dustin Getz10:08:32

it is complimentary, rama data tier to electric UI tier is a native fit

👍 6
Dustin Getz10:08:44

in theory at least

danielstockton10:08:34

Am i right that its a framework for streaming incremental updates to data similar to differential dataflow then?

Dustin Getz10:08:03

i don’t know if rama “subindexing” is actually differential dataflow or not (i doubt it and i think that’s probably a mistake). I can say that the next major version of Electric is indeed differential dataflow

👍 2
Dustin Getz10:08:57

certainly rama is similar to and competitive w DD

👍 2
mmer10:08:46

It is no coincident that when you see the Paths in the demo that they look like Specter, because they are both supported by the team. I would think this could mean that a RAMA Clojure implementation could be on the cards.

henrik11:08:04

Rama is written in Clojure, so it already exists. The “native” API isn’t exposed though.

mmer11:08:09

I was just looking at the video where it is all Java, seeing the groovy examples, immediately makes you want to usea Clojure variant as it would be much simpler.

henrik11:08:12

Like, they call $$… “vars”. Probably because they are vars in the Clojure API language.

mmer11:08:20

The code that is written in their examples, is really hard to understand as you are using one language to write another, I would image that macros, could be created to make it simpler to create the code more directly. Having worked in solutions where you are generating code from code, you find the debugging the emitted code is complex as you don't have the support of IDE etc.

henrik11:08:12

@dustingetz Do you imagine subindexing being DD or not would surface in the API, or more of a mistake regarding the efficiency of the internals?

henrik11:08:19

The examples certainly do some contortions due to (I’m guessing) the limitations of Java. Variables being strings beginning with */`$$`, and if you want a literal string with those prefixes you must wrap them in Constant comes to mind.

Ben Sless11:08:02

I stumbled on a line where they mentioned the importance of ordering, which is a prerequisite for timely dataflow

👀 2
Dustin Getz11:08:41

the differential dataflow end user programming model, as I understand it and as we are implementing it in Electric, is FRP like and DAG based (in Electric you build your app out of lambdas and closures). As opposed to the Rama end user programming model where you program with structs and you have to plan out the data structures. My initial impression is Rama feels like intrusive data structures in C as opposed to generic data structures in Java. Faster but lower level, used in operating systems and games. https://stackoverflow.com/questions/5004162/what-does-it-mean-for-a-data-structure-to-be-intrusive

👀 2
💡 2
Ben Sless14:08:33

DD exists at a different abstraction level, too. It's about sets (signals? maybe), not about a specific value or a specific time

Kein07:08:17

What is recommended to NOT update a value if the input is under some condition? Do we have to save the previous value in a state?

xificurC08:08:04

can you be more specific?

Kein08:08:24

When syncing text data from server to client, we need to keep track of whether the server’s data is outdated by comparing versions. If the incoming data is outdated, then the client state won’t be updated. To prevent a function from being updated in electric context , we need to calculate the previous value. Is there a recommended way to achieve this ?

Kein08:08:24

Say we have:

(e/defn my-func [a b] (+ a b) )
In state1 , a=1, b=1, result = 2 In state2 , a=2, b=1, result = 3 Can we block state2's update under some condition, and let the result remain 2? Such as :
(e/defn my-func [a b] (if block-condition previous-value (+ a b) )

xificurC08:08:22

> When syncing text data from server to client, we need to keep track of whether the server’s data is outdated by comparing versions How does this situation arise? Electric doesn't produce out-of-order data. Is your server sending old data to the UI?

xificurC08:08:36

to also answer the broader question (how to act on previous values), it is typical to introduce state here (an atom)

(let [!v (atom 0)]
  (when condition (reset! !v (+ a b)))
  (e/watch !v))

Kein09:08:28

> How does this situation arise? Electric doesn’t produce out-of-order data. Is your server sending old data to the UI? The outdatedness is defined in our application domain, not in system level. We try to implement an offline-first model where there are n+1 copies of data. And we annotate different copies by version and event-time to resolve conflict.

Kein09:08:08

> to also answer the broader question (how to act on previous values), it is typical to introduce state here (an atom) Ah I see. Thanks for showing this method.

😉 2
Dustin Getz12:08:58

note that state and recursion are deeply related and/or the same thing. Consider a Java for loop with mutable accumulator vs reduce

Dustin Getz12:08:32

So, I hope next year we can eliminate the need for an atom here, but it is not a priority, and i don't find the atom to be problematic for this reason

👀 2
Kein12:08:47

Okay, I see your point

danbunea12:08:19

I am trying to run the electric-datomic-browser, but I can't get shadow-cljs to compile the front

clj -A:dev -X user/main
Starting Electric compiler and server...
shadow-cljs - server version: 2.25.3 running at 
shadow-cljs - nREPL server started on port 9001
[:dev] Configuring build.
[:dev] Compiling ...
[:dev] Build failure:
------ ERROR -------------------------------------------------------------------
 File: jar:file:/Users/danbunea/.m2/repository/thheller/shadow-cljs/2.25.3/shadow-cljs-2.25.3.jar!/shadow/cljs/devtools/client/hud.cljs:1:1
--------------------------------------------------------------------------------

   1 | (ns shadow.cljs.devtools.client.hud
-------^------------------------------------------------------------------------
Invalid :refer, var goog.string/format does not exist
--------------------------------------------------------------------------------
   2 |   (:require
   3 |     [shadow.dom :as dom]
   4 |     [shadow.animate :as anim]
   5 |     [shadow.cljs.devtools.client.env :as env]
--------------------------------------------------------------------------------

WARN  app.server: Port 8080 was not available, retrying with 8081

:point_right: App server available at 

danbunea12:08:51

I did see that in deps.edn, we have an older version:

:dev
  {:extra-paths ["src-dev"] ; fix uberjar java.lang.NoClassDefFoundError: clojure/tools/logging/impl/LoggerFactory
   :extra-deps
   {...
    thheller/shadow-cljs {:mvn/version "2.22.10"}}

Geoffrey Gaillard12:08:37

We saw this. We suspect a corrupted .shadow-cljs cache. We are not yet clear on the root cause. You can try to • stop the shadow process (not just the build, stop the JVM) • rm -rf .shadow-cljs • restart the shadow build It’s worth noting we only saw this behavior with watch, never with compile or release

danbunea13:08:03

Hey, thanks for answering: • remove the cache .shadow-cljs • stopped everything • 🔴 clj -A:dev -X user/main

danbunea13:08:26

failure again, same error

Geoffrey Gaillard13:08:52

I see WARN app.server: Port 8080 was not available, retrying with 8081, could there be a second shadow process running ?

danbunea13:08:46

no, that's an nginx, I can try without it

danbunea13:08:52

from something else

Geoffrey Gaillard13:08:03

I can reproduce the issue with a clean repo clone. Looking into it

danbunea13:08:42

thank you. My use case of using electric is very much dependent on datomic so I thought I could start from this example

Dustin Getz14:08:58

I do not recommend starting here, we haven't touched it in ages

Dustin Getz14:08:32

I did host it this week, and am working on unifying how builds work in our dozen or so repos this week

Dustin Getz14:08:52

but your first day using electric should be the starter app

👍 2
Dustin Getz15:08:39

Though now that Datomic is free, I'll make the starter app use Datomic

Dustin Getz15:08:02

If you want the contributor badge lmk and you can send a PR if you like

Geoffrey Gaillard15:08:22

We have a minimal repro for the shadow build issue. We’ll address it. Thank you for the report 👍

👀 2
👍 2
denik19:08:59

running into the same issue

henrik07:08:31

FWIW, I’m getting this with v2-alpha-422-g96e5c0a5 as well.

henrik07:08:15

We clear .shadow-cljs on every start, so that’s not a viable workaround, it seems.

xificurC12:08:28

I pushed a new commit which fixed this on my end, can someone from this thread double check on their end? The git coords to use are

io.github.hyperfiddle/electric {:git/sha "22937f751db7258ac0be0337833c036dbc4879f2"}

🙏 2
denik18:08:08

fixed it for me

🙏 2
xificurC19:08:50

new maven coordinates are out

🙏 2
xificurC15:08:46

since an e/fn has the same structure as a clojure fn it should be doable. I'm not sure how malli checks functions and how much effort it would be though. Are you familiar with malli's internals?

Garrett Hopper16:08:02

I am not unfortunately; I'm just now trying to integrate it. I may resort to just using instrumented validation inside the functions for arguments, as many of the other feature of function schemas likely aren't applicable to Electric. (Generative testing, generation functions, etc.)

Garrett Hopper16:08:27

Most of the function schema/instrumentation functionality likely won't work, as Malli expects to work with plain functions which it can wrap in another fn. (Which then wouldn't have the Electric metadata)

Garrett Hopper17:08:22

Or varargs for that matter

Dustin Getz17:08:10

varargs are actually just released yesterday to clojars i haven’t announced it yet, see change log

Dustin Getz17:08:47

condition. maps- no, i’ll add a ticket. Our compat maturity is currently adhoc we implement what we need. When we come up for air (after differential electric, incremental builds, optimistic ui) we will perhaps make a compatibility matrix and ratchet it down

Garrett Hopper17:08:24

👌 Makes sense; thank you 🙂

Dustin Getz17:08:53

v2-422 (about to announce) has several compat impeovements

🙌 2
Garrett Hopper18:08:56

This feels like a reasonable usage for Malli validation of function inputs

(e/defn Button [& args]
  (let [schema      [:catn [:x string?]]
        {:keys [x]} (m/coerce schema args)]
    (dom/button
      (dom/text (str "Click me! " x)))))
It doesn't provide the same niceties as Malli's builtin function schemas though. (Output validation, clj-kondo integration, generators, etc.)

Garrett Hopper17:08:53

@dustingetz, seems like something may have changed with v2-422; I'm getting an error where dom/new-node is being called with a parent of a blank string despite having a wrapping binding to a real dom node. (See https://github.com/garrett-hopper/ElectricStorybookExamples/blob/main/src/utils/storybook.clj#L17) (User error)

Failed to execute 'appendChild' on 'Node': This node type does not support this method.

in ( hyperfiddle.electric-dom2/new-node #object[Text [object Text]] button ) in app/books.cljc
in case default branch
in (case 1 ...)
in reactive (defn Button nil ...) in app/books.cljc line 16
in ( hyperfiddle.electric-dom2/-googDomSetTextContentNoWarn #object[Text [object Text]] <exception> ) in app/books.cljc
in case default branch
in (case 0 ...)
in reactive (fn nil ...)
in (try ...)

Garrett Hopper17:08:14

I can come up with a minimal reproduction if it's not immediately clear what might've changed about this behavior.

Dustin Getz17:08:30

we will take a look, might not be until tomorrow sorry 😕

Dustin Getz17:08:32

from a quick glance - that's a lazy seq, does making it strict fix the issue?

Dustin Getz17:08:12

i dont see how that could actually be something that changed

Garrett Hopper17:08:19

The bindings? I don't think they have anything to do with it.

Garrett Hopper17:08:30

Ok, I'll verify that it's the version change that caused it.

Dustin Getz17:08:51

Oh, i believe that it could be a breaking change in v2-422

Garrett Hopper17:08:01

Hmm, this does appear to work in isolation, so it may not be the initial problem I thought it was.

(def start-reactor
  (let [node (js/document.createElement "div")]
    (.appendChild js/document.body node)
    (e/boot
     (binding [dom/node node]
        (core/App.)))))

Garrett Hopper17:08:19

I'm not sure why the dom/node binding isn't working as expected in the full example though. :thinking_face:

Dustin Getz17:08:59

is there react/reagent interop?

Garrett Hopper17:08:18

No, nothing like that

Garrett Hopper17:08:46

Adding a breakpoint here, is it expected that a child text type would be called first before the parent button?

Garrett Hopper17:08:35

parent is undefined for both calls 😕

Dustin Getz17:08:43

parent should not be undefined

Dustin Getz17:08:39

i dont seem to be looking at the same code as you because core/App is not defined

Garrett Hopper17:08:15

I'm sorry. I think I was just being dumb and had a component being mounted inside dom/text. :man-facepalming:

Garrett Hopper17:08:06

Not in the macro I sent you, but where I'm using it, at some point I'd tried to test something and apparently ended up with

(defbook Button
  (dom/text (Button. 5)))

Garrett Hopper17:08:24

So when Button when to mount, it obviously didn't have a parent since it's in a text node

Dustin Getz17:08:46

and you're saying this worked in v2-349?

Garrett Hopper17:08:48

No, I don't believe so. I think it was around the same time that I had upgraded that I was also doing the :pre/:post testing and instead of Button, I had an electric function that actually returned a string rather than trying to mount child components. Entirely user error; I apologize 😕

👍 4