Electric Clojure v3: Differential Dataflow for UI [video]
hyperfiddle-docs.notion.site
hyperfiddle-docs.notion.site
Not Dustin though, if anything he seems to have doubled down and sought to prove the ideas with working libraries. Here we are years later with a growing community and excitement about this real working software.
Bravo Dustin for having the strength to stay the course. You knew you were on to something way back then, and haven't lost sight of it.
On the other hand, there is a ridiculous amount of power in Lisps. Operating at a higher level of abstraction is like having magical powers. When you go back to programming in Go, you feel like someone chopped off one of your arms or legs.
On the other hand, to be fair to Go, when you are an immigrant in a Go codebase, everything is very easy. Turn this way ... oh I've seen this a thousand times before its an XYZ. Turn that way, oh, I don't even have to read that, I know what that is. Oh what is that ... oh its my greencard to this codebase, I can now fix bugs with confidence.
And that's OK, not every language needs to cater to everyone :)
You can do absolutely magical interactive apps with Electric. However, I think, the catch is that the learning curve of Missionary and Electric is _very_ steep, and you have to understand the concepts pretty deeply in order to successfully debug (hence, to make) your code. And it is worth it, in my opinion — the concepts used are beautiful in a mathematical way and fit together pretty well.
[1] https://hyperfiddle-docs.notion.site/Electric-Clojure-v3-tea...
I haven't got enough time to finish the video today, but I'd like to throw out some of our major technical domain problems and see how people familiar with electric would try to solve them:
1. How does it handle multiple different functionality servers? And potentially server-server networking? e.g. if I need to get some data from server A and some other data from server B, and combine them into the UI, is that doable in electric? Also what if I want to save the combined data onto the database on server C? Now for simplicity, let's just assume we're talking about less than 20 servers here and there's no problem with the port, so each one of them can keep 1 websocket per each other server open up. 2. How does it handle listening to a stream of events and triggering if the event meets a certain criteria? e.g. like if the price of GOOG is lower than a certain price then trigger a popup or something like that. This may not be the intended scope of electric Clojure but it is what we do day to day, so I'm curious. 3. How does it handle interop with existing Java apps? Is it just a normal java-clj interop?
Overall I really like the concept of being network transparent, or shall I say that devs would be better served by spending more time on business data and function, but not on coming up with derived request/response obj, API route, etc. Also, if this type of library ends up in some sort of more mainstream programming language, I would vouch for adoption in a heartbeat.
2- Electric vars are reactive signals and stock prices are signals, so: (if (< price target) ($ Modal))
1- we can support microservice topologies (N sites) in principle though the work requires a corporate design partner to make sure we get it right.
This is an important capability for the Hyperfiddle layer - a Universal UI is only as valuable as the data it reaches; i.e., as far as RAD platforms go, service connectivity is the only thing that matters. Today, connecting a service to a UI requires immeasurable amounts of glue code (constantly shifting, breaking, requiring ongoing maintenance) at the cost of ~$100k+ per year per service connection, and costs grow superlinearly with complexity of the service! Electric collapses to zero the cost of service connectivity.
In approaches with an explicit API, you can explicitly maintain backward compatibility for a period of time until you believe that enough browsers have "caught up" to newer versions of the software running on the server to allow you to retire that backward compatibility.
It's so incredibly brilliant that it spoils me for mainstream tech, but it's never actually "done enough" for mgmt to feel comfortable adopting it in any capacity. (See also: Unison https://www.unison-lang.org/)
Or, you can get them to adopt, but then you hit hiring limitations.
I say this as someone who built a large team of Clojure developers in a Fortune 100 company, and lived to regret it.
Depends on how flexible your "hiring principles" are. You mention you did this at a Fortune 100 company, so obviously you didn't have any flexibility at all. But for the (good) places that allow people to learn on the job, you just need to find a sufficiently smart person who likes to learn, and they'll get up to speed with Clojure relatively quickly, as long as their first reaction when seeing parenthesis isn't "eww".
Things really are, and can be that simple. Rich has some talks that should be required viewing for all programmers.
Turns out that I miss all the conveniences whenever I'm not using a lisp-like.
Agree with the challenges you mentioned using new tech at a more mainstream company.
Like try finding YC job posting without TS and/or Python in the requirements :-/
Copilot and other ai powered autocompletes mostly amount to large boilerplate generators. macros in lisp and elxiir are strictly better in terms of long term maintenence in the cases that warrant that. But autogenerated code with a junior checking it in under "trust me bro" does not instill confidence in me.
I'm reminded of a situation a few months ago with my (nontechnical) cofounder. He had been discussing our recent funding round and strategies for growth with another CTO friend he knew. The approach he was a proponent of was hiring a bunch of gifted juniors and watching them like hawks. The idea is that they would produce lots of code for all the upcoming features. The problem here is obvious. Juniors make messes and more code != better code. These new AI powered pushes seem to basicly be this strategy on steroids. I can certainly see the strength of it if you're trying to build fast, get traction and get acquired before the house of cards falls apart.
Personally, I don't see it as a optimal strategy when you're trying to build a viable long term business. Platform matters. Ergonomics matter and most importantly, Human intelligence matters. We hired another senior engineer instead and we're doing fine. If I had to do it over again, I would still stick with elixir.
- locally reasoned about (pure functions that compose)
- static type checking (so that nonsense is discovered at compile time)
Weirdly this makes Python a worse choice and Haskell a better one. Clojure benefits a little bit.
I don’t think people are choosing language stacks based on what is best for the problem. The main factor is “what do I already know?”
Rich Hickey once called TDD "guardrail programming", because it's like navigating to a location by first making contact with the guardrail and then just letting go of the steering wheel.
What they value is "hammock time", which is similar to what used to be called "Turinging" by the people working with Alan Turing, you just stare at the problem for hours and weeks and months, until a solution seems to magically manifest itself as obvious. Or in Richs' case, you just lay in a hammock with your books and a laptop, and think about your problem domain until the solution becomes easy.
Clojure itself is a tool that is meant to be refine- and hone-able by a skilled user, but can create a mess in the hands of a junior/AI.
Case in point, I've been working on the same 20k lines of code for the last 5 Years, and I don't mind it because I feel like I'm gaining another meaningful insight into my problem domain every few weeks. I'm not even working in Clojure anymore but the mindset is something that stuck to me.
I believe there is a macro for that.
The whole point of explorative REPL based programming is that it allows you to further your understanding of the problem. And the value is in the understanding, and not the code.
I feel like this is best captured by a story that my father (an artist/designer) once told me.
The Shogun and the Artist
Once, a young shogun visited Mount Fuji and was so captivated by its beauty that he commissioned a renowned local artist to create an ink wash painting of the mountain. The shogun instructed the artist to complete the painting by the time he returned from his travels.
Years passed, and the shogun finally returned, eager to see the painting. However, the artist apologized, explaining that he had not yet finished. The shogun, though disappointed, granted the artist more time.
More years went by, and the shogun returned again, only to find the painting still unfinished. Yet again, the shogun, moved by the mountain's beauty, granted another extension.
This pattern repeated for many years, until both the shogun and the artist had grown old. Finally, the shogun, tired of waiting, demanded the artist finish the painting.
“We are both old and frail,” the shogun said. “I may not live to see the day you finish. I want to visit Mount Fuji through your painting, even if I can no longer travel. Finish it now—I demand it.”
“Very well,” the artist replied. He took up his brush and, in just a few strokes, perfectly captured the essence of Mount Fuji.
The shogun was astonished. “This captures the essence perfectly,” he said. “But if it took only moments, why did you make me wait a lifetime? Do you think so little of me?”
The artist smiled and replied, “On the contrary, my lord. I have painted Mount Fuji every day since you first made your request. But it took a lifetime of learning, experimentation, and experience to capture its essence like this.”
Understanding at last, the shogun rewarded the artist for his lifetime of dedication.I think Clojure (or any lisp) applications tend to lean towards mini-DSLs that don’t lend themselves to generalization very well. There’s also not much agreement in the community on any single problem or way of doing anything (one of the best and worst parts about the language)
The most optimistic outlook I've heard on AI+Clojure is that AI is not magical and still benefits from good abstractions. So hopefully as models get better at reasoning, using something like Electric Clojure will help them write clean, maintainable code.
LiveView does serverside rendering and “streams” dom diffs afaik
Whereas electric is doing full client side rendering and sending data/code diffs “only”
Writing client side code seems lower friction in electric at the cost of the macro/client magic being a little more magic
This feels over engineered to me. The classic cool factor influencing the “if we could vs if we should”, bolstered by developing it as a software engineer on a performant dev box with impeccable internet connection; hell, maybe even a local dev db clone.
I suppose you could argue server side caching would alleviate some of the pains of a design like this, but you’re still performing redundant server round trips for the stream when a piece of data goes in and out of view.
Does the tech have any local caching options to make this a little more sane for a worst case an end user?
This is not the way how Electric apps work, this was just a demonstration of how easily you can orchestrate complex network-transparent client-server interactions _when_ you need them.
> Does the tech have any local caching options to make this a little more sane for a worst case an end user?
This is trivially solved in Electric- as long as you hold onto a client-side handle of the data, it won't be unmounted.
Re. server caching – Electric (being reactive) auto memoizes all scopes, even on the server. (The essence of reactive programming is a time/space tradeoff - cache more things to minimize recomputation later.) For example, in the virtual scroll demo from the talk, the database query runs once and is retained in memory, so that scrolling simply indexes over the memoized collection:
(defn window [xs offset limit]
(subvec (vec xs) ; fast cast
(Math/max offset 0)
(Math/min (+ offset limit) (count xs))))
(e/defn Window [query! offset limit]
(e/server
(let [xs ($ e/Offload #(query!))] ; retain and reuse xs as offset changes
[(count xs) (e/diff-by identity (window xs offset limit))])))
Regarding client caching - it's basically the same. Hoist the value you want to save to be above the conditional that is disposing it. If that doesn't work, use an atom.