Last active
March 16, 2023 08:00
-
-
Save xificurC/720868fb6c43ba0cbfc6f7338a526a4f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" | |
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> | |
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> | |
<head> | |
<!-- 2023-03-16 Št 08:40 --> | |
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1" /> | |
<title>Why Electric Clojure is a big deal</title> | |
<meta name="author" content="Peter Nagy" /> | |
<meta name="generator" content="Org Mode" /> | |
<style> | |
#content { max-width: 60em; margin: auto; } | |
.title { text-align: center; | |
margin-bottom: .2em; } | |
.subtitle { text-align: center; | |
font-size: medium; | |
font-weight: bold; | |
margin-top:0; } | |
.todo { font-family: monospace; color: red; } | |
.done { font-family: monospace; color: green; } | |
.priority { font-family: monospace; color: orange; } | |
.tag { background-color: #eee; font-family: monospace; | |
padding: 2px; font-size: 80%; font-weight: normal; } | |
.timestamp { color: #bebebe; } | |
.timestamp-kwd { color: #5f9ea0; } | |
.org-right { margin-left: auto; margin-right: 0px; text-align: right; } | |
.org-left { margin-left: 0px; margin-right: auto; text-align: left; } | |
.org-center { margin-left: auto; margin-right: auto; text-align: center; } | |
.underline { text-decoration: underline; } | |
#postamble p, #preamble p { font-size: 90%; margin: .2em; } | |
p.verse { margin-left: 3%; } | |
pre { | |
border: 1px solid #e6e6e6; | |
border-radius: 3px; | |
background-color: #f2f2f2; | |
padding: 8pt; | |
font-family: monospace; | |
overflow: auto; | |
margin: 1.2em; | |
} | |
pre.src { | |
position: relative; | |
overflow: auto; | |
} | |
pre.src:before { | |
display: none; | |
position: absolute; | |
top: -8px; | |
right: 12px; | |
padding: 3px; | |
color: #555; | |
background-color: #f2f2f299; | |
} | |
pre.src:hover:before { display: inline; margin-top: 14px;} | |
/* Languages per Org manual */ | |
pre.src-asymptote:before { content: 'Asymptote'; } | |
pre.src-awk:before { content: 'Awk'; } | |
pre.src-authinfo::before { content: 'Authinfo'; } | |
pre.src-C:before { content: 'C'; } | |
/* pre.src-C++ doesn't work in CSS */ | |
pre.src-clojure:before { content: 'Clojure'; } | |
pre.src-css:before { content: 'CSS'; } | |
pre.src-D:before { content: 'D'; } | |
pre.src-ditaa:before { content: 'ditaa'; } | |
pre.src-dot:before { content: 'Graphviz'; } | |
pre.src-calc:before { content: 'Emacs Calc'; } | |
pre.src-emacs-lisp:before { content: 'Emacs Lisp'; } | |
pre.src-fortran:before { content: 'Fortran'; } | |
pre.src-gnuplot:before { content: 'gnuplot'; } | |
pre.src-haskell:before { content: 'Haskell'; } | |
pre.src-hledger:before { content: 'hledger'; } | |
pre.src-java:before { content: 'Java'; } | |
pre.src-js:before { content: 'Javascript'; } | |
pre.src-latex:before { content: 'LaTeX'; } | |
pre.src-ledger:before { content: 'Ledger'; } | |
pre.src-lisp:before { content: 'Lisp'; } | |
pre.src-lilypond:before { content: 'Lilypond'; } | |
pre.src-lua:before { content: 'Lua'; } | |
pre.src-matlab:before { content: 'MATLAB'; } | |
pre.src-mscgen:before { content: 'Mscgen'; } | |
pre.src-ocaml:before { content: 'Objective Caml'; } | |
pre.src-octave:before { content: 'Octave'; } | |
pre.src-org:before { content: 'Org mode'; } | |
pre.src-oz:before { content: 'OZ'; } | |
pre.src-plantuml:before { content: 'Plantuml'; } | |
pre.src-processing:before { content: 'Processing.js'; } | |
pre.src-python:before { content: 'Python'; } | |
pre.src-R:before { content: 'R'; } | |
pre.src-ruby:before { content: 'Ruby'; } | |
pre.src-sass:before { content: 'Sass'; } | |
pre.src-scheme:before { content: 'Scheme'; } | |
pre.src-screen:before { content: 'Gnu Screen'; } | |
pre.src-sed:before { content: 'Sed'; } | |
pre.src-sh:before { content: 'shell'; } | |
pre.src-sql:before { content: 'SQL'; } | |
pre.src-sqlite:before { content: 'SQLite'; } | |
/* additional languages in org.el's org-babel-load-languages alist */ | |
pre.src-forth:before { content: 'Forth'; } | |
pre.src-io:before { content: 'IO'; } | |
pre.src-J:before { content: 'J'; } | |
pre.src-makefile:before { content: 'Makefile'; } | |
pre.src-maxima:before { content: 'Maxima'; } | |
pre.src-perl:before { content: 'Perl'; } | |
pre.src-picolisp:before { content: 'Pico Lisp'; } | |
pre.src-scala:before { content: 'Scala'; } | |
pre.src-shell:before { content: 'Shell Script'; } | |
pre.src-ebnf2ps:before { content: 'ebfn2ps'; } | |
/* additional language identifiers per "defun org-babel-execute" | |
in ob-*.el */ | |
pre.src-cpp:before { content: 'C++'; } | |
pre.src-abc:before { content: 'ABC'; } | |
pre.src-coq:before { content: 'Coq'; } | |
pre.src-groovy:before { content: 'Groovy'; } | |
/* additional language identifiers from org-babel-shell-names in | |
ob-shell.el: ob-shell is the only babel language using a lambda to put | |
the execution function name together. */ | |
pre.src-bash:before { content: 'bash'; } | |
pre.src-csh:before { content: 'csh'; } | |
pre.src-ash:before { content: 'ash'; } | |
pre.src-dash:before { content: 'dash'; } | |
pre.src-ksh:before { content: 'ksh'; } | |
pre.src-mksh:before { content: 'mksh'; } | |
pre.src-posh:before { content: 'posh'; } | |
/* Additional Emacs modes also supported by the LaTeX listings package */ | |
pre.src-ada:before { content: 'Ada'; } | |
pre.src-asm:before { content: 'Assembler'; } | |
pre.src-caml:before { content: 'Caml'; } | |
pre.src-delphi:before { content: 'Delphi'; } | |
pre.src-html:before { content: 'HTML'; } | |
pre.src-idl:before { content: 'IDL'; } | |
pre.src-mercury:before { content: 'Mercury'; } | |
pre.src-metapost:before { content: 'MetaPost'; } | |
pre.src-modula-2:before { content: 'Modula-2'; } | |
pre.src-pascal:before { content: 'Pascal'; } | |
pre.src-ps:before { content: 'PostScript'; } | |
pre.src-prolog:before { content: 'Prolog'; } | |
pre.src-simula:before { content: 'Simula'; } | |
pre.src-tcl:before { content: 'tcl'; } | |
pre.src-tex:before { content: 'TeX'; } | |
pre.src-plain-tex:before { content: 'Plain TeX'; } | |
pre.src-verilog:before { content: 'Verilog'; } | |
pre.src-vhdl:before { content: 'VHDL'; } | |
pre.src-xml:before { content: 'XML'; } | |
pre.src-nxml:before { content: 'XML'; } | |
/* add a generic configuration mode; LaTeX export needs an additional | |
(add-to-list 'org-latex-listings-langs '(conf " ")) in .emacs */ | |
pre.src-conf:before { content: 'Configuration File'; } | |
table { border-collapse:collapse; } | |
caption.t-above { caption-side: top; } | |
caption.t-bottom { caption-side: bottom; } | |
td, th { vertical-align:top; } | |
th.org-right { text-align: center; } | |
th.org-left { text-align: center; } | |
th.org-center { text-align: center; } | |
td.org-right { text-align: right; } | |
td.org-left { text-align: left; } | |
td.org-center { text-align: center; } | |
dt { font-weight: bold; } | |
.footpara { display: inline; } | |
.footdef { margin-bottom: 1em; } | |
.figure { padding: 1em; } | |
.figure p { text-align: center; } | |
.equation-container { | |
display: table; | |
text-align: center; | |
width: 100%; | |
} | |
.equation { | |
vertical-align: middle; | |
} | |
.equation-label { | |
display: table-cell; | |
text-align: right; | |
vertical-align: middle; | |
} | |
.inlinetask { | |
padding: 10px; | |
border: 2px solid gray; | |
margin: 10px; | |
background: #ffffcc; | |
} | |
#org-div-home-and-up | |
{ text-align: right; font-size: 70%; white-space: nowrap; } | |
textarea { overflow-x: auto; } | |
.linenr { font-size: smaller } | |
.code-highlighted { background-color: #ffff00; } | |
.org-info-js_info-navigation { border-style: none; } | |
#org-info-js_console-label | |
{ font-size: 10px; font-weight: bold; white-space: nowrap; } | |
.org-info-js_search-highlight | |
{ background-color: #ffff00; color: #000000; font-weight: bold; } | |
.org-svg { } | |
</style> | |
</head> | |
<body> | |
<div id="content" class="content"> | |
<h1 class="title">Why Electric Clojure is a big deal</h1> | |
<div id="table-of-contents" role="doc-toc"> | |
<h2>Table of Contents</h2> | |
<div id="text-table-of-contents" role="doc-toc"> | |
<ul> | |
<li><a href="#orgf94278e">1. Reactive language</a></li> | |
<li><a href="#org01528b0">2. Multi-peer language</a></li> | |
<li><a href="#org0a76740">3. Conclusion</a></li> | |
</ul> | |
</div> | |
</div> | |
<p> | |
Disclaimer: I work for Hyperfiddle. | |
</p> | |
<p> | |
<a href="https://github.com/hyperfiddle/electric">Electric Clojure</a> is out. The clojure(script) community is excited! Our primary | |
target is dynamic web applications, so users naturally compare it to React and | |
other frontend frameworks. I’ll claim no framework is <b>even close</b> to feature | |
parity. Let me explain the key differences. | |
</p> | |
<div id="outline-container-orgf94278e" class="outline-2"> | |
<h2 id="orgf94278e"><span class="section-number-2">1.</span> Reactive language</h2> | |
<div class="outline-text-2" id="text-1"> | |
<p> | |
React, Solid and others provide a reactive framework. We see signals and similar | |
abstractions pop up and an API around them. Electric Clojure is a reactive | |
<i>language</i>. It looks and feels like Clojure but it is a new language. <code>if</code> is a | |
reactive if, <code>try</code> is a reactive try. This means everything is a signal! The | |
simplicity this brings to the user is a big thing. You don’t have to mix | |
Javascript code, JSX code, framework code - it’s one thing! Less is more. | |
</p> | |
</div> | |
</div> | |
<div id="outline-container-org01528b0" class="outline-2"> | |
<h2 id="org01528b0"><span class="section-number-2">2.</span> Multi-peer language</h2> | |
<div class="outline-text-2" id="text-2"> | |
<p> | |
I don’t think anyone understands this fully yet. Electric Clojure compiles a | |
single program and distributes it across 2 peers. It takes your client/server | |
code, splits it in half and <i>derives</i> all the network communication. What people | |
think of here is request/response and RPC, because that’s what they know. But | |
Electric Clojure compiles down to a graph and runs everything concurrently. | |
Let’s disect a toy example: | |
</p> | |
<div class="org-src-container"> | |
<pre class="src src-clojure"><span style="color: #51afef;">(</span><span style="color: #ECBE7B;">e</span>/client | |
<span style="color: #c678dd;">(</span><span style="color: #51afef;">let</span> <span style="color: #98be65;">[</span>saying <span style="color: #a9a1e1;">(</span>get-saying-from-user<span style="color: #a9a1e1;">)</span><span style="color: #98be65;">]</span> | |
<span style="color: #98be65;">(</span><span style="color: #ECBE7B;">e</span>/server <span style="color: #a9a1e1;">(</span><span style="color: #51afef;">let</span> <span style="color: #51afef;">[</span>id <span style="color: #c678dd;">(</span>gen-next-db-id<span style="color: #c678dd;">)</span><span style="color: #51afef;">]</span> | |
<span style="color: #51afef;">(</span>transact-saying! id saying<span style="color: #51afef;">)</span><span style="color: #a9a1e1;">)</span><span style="color: #98be65;">)</span><span style="color: #c678dd;">)</span><span style="color: #51afef;">)</span> | |
</pre> | |
</div> | |
<p> | |
<code>get-saying-from-user</code> is a user input on the client side. <code>gen-next-db-id</code> is a | |
database request for a new ID on the server side. There is no dependency between | |
these 2 calls. That means they will run concurrently! <code>transact-saying!</code> will | |
wait for both to finish. | |
</p> | |
<p> | |
Think about a way to achieve the same behavior in Javascript. The best I can | |
think of is | |
</p> | |
<div class="org-src-container"> | |
<pre class="src src-javascript"><span style="color: #51afef;">const</span> [<span style="color: #dcaeea;">saying</span>, <span style="color: #dcaeea;">id</span>] = <span style="color: #51afef;">await</span> Promise.all([getSayingFromUser(), genNextDbId()]); | |
<span style="color: #51afef;">await</span> transactSaying(id, saying); | |
</pre> | |
</div> | |
<p> | |
which is operationally not the same thing: | |
</p> | |
<ul class="org-ul"> | |
<li>the <code>id</code> roundtrips for no reason</li> | |
<li>if a promise fails the second one stays running even though we don’t care | |
anymore</li> | |
</ul> | |
<p> | |
It’s even worse from the coding perspective. To implement the above you have to: | |
</p> | |
<ul class="org-ul"> | |
<li>create a <code>genNextDbID</code> on the frontend. This will have to issue an e.g. HTTP | |
or websocket request</li> | |
<li>now you need to open the backend code and add a request handler</li> | |
</ul> | |
<p> | |
This will result in a lot of boilerplate, just to orchestrate the network | |
roundtrip we didn’t even care about! The ID could have stayed on the backend. “I | |
know how to fix that!”, you shout. | |
</p> | |
<div class="org-src-container"> | |
<pre class="src src-javascript"><span style="color: #51afef;">const</span> <span style="color: #dcaeea;">requestId</span> = <span style="color: #51afef;">new</span> <span style="color: #ECBE7B;">RequestId</span>(); | |
genNextDbId(requestId); | |
<span style="color: #51afef;">const</span> <span style="color: #dcaeea;">saying</span> = <span style="color: #51afef;">await</span> getSayingFromUser(); | |
<span style="color: #51afef;">await</span> transactSaying(saying, requestId); | |
</pre> | |
</div> | |
<p> | |
Now the server can tuck the ID away under the <code>requestId</code> key and reconcile the | |
data. However this just creates more trouble: | |
</p> | |
<ul class="org-ul"> | |
<li><code>genNextDbId</code> is now fire and forget. What if it fails?</li> | |
<li>what if <code>getSayingFromUser</code> fails? How long will the server keep the generated | |
id around?</li> | |
<li>how much more orchestration code did we have to write on both peers?</li> | |
</ul> | |
<p> | |
I hope you start to see a pattern here. Any code you come up will involve | |
writing a lot of boilerplate code and thinking of tons of edge cases. | |
</p> | |
<p> | |
Now look at the Electric Clojure implementation again. It’s dead-simple code to | |
write and <b>none</b> of these concerns exist! This is why we compare it to garbage | |
collection - managed memory solves a lot of memory orchestration for you in a | |
safe way and allows new patterns that weren’t possible with manual memory | |
management. The same applies here for the network - you don’t write manual | |
network orchestration code and get optimal concurrency as a bonus. | |
</p> | |
</div> | |
</div> | |
<div id="outline-container-org0a76740" class="outline-2"> | |
<h2 id="org0a76740"><span class="section-number-2">3.</span> Conclusion</h2> | |
<div class="outline-text-2" id="text-3"> | |
<p> | |
Electric Clojure is a big deal, even if you are not a Clojure(script) | |
programmer. Of course, this is just the beginning! There’s more exciting work | |
underway, so stay tuned! | |
</p> | |
</div> | |
</div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment