The following conversation about Hoplon is from Clojurians Slack discussion on June 6th, 2016. It was saved and posted here by Alan Dipert.
mhr 23:24:53

I'm having trouble finding a good explanation for how Hoplon and Reagent/Re-frame compare. I know Hoplon uses a "spreadsheet" metaphor with Javelin and doesn't use React, and Reagent uses FRP and React, but spreadsheets are sort of FRP, aren't they? How does the metaphor diverge? I want to make an app where much of the code I could reuse for the web, my phone, and the desktop, though this will most likely be a desktop-first app via Electron. I want to make a personal knowledge base (https://en.wikipedia.org/wiki/Personal_knowledge_base), so I want to be able to query and explore a multimedia graph database rendered in canvas or WebGL (I'm not sure of the GUI side yet, but the data structures side is pretty well-defined for me). I want to have the database be immutable and versioned, so I've been looking into using DataScript as the base for my database. How would DataScript fit into Hoplon or Reagent?
Can you help me understand the pros and cons or differences between Hoplon and Reagent for someone who hasn't used React or ClojureScript before? I use JS a lot, but I don't have any real experience with the React or ClojureScript ecosystems nor even Clojure, so I don't quite know why I would want CLJS-flavored React any more than I know that I would want Javelin. I apologize for requesting such a comprehensive explanation from such ignorance. There's so much information to understand that it's difficult for a newcomer like me to make sense of it and make a good judgment on which framework/library is best for me. I have dabbled in different Lisps but haven't used any for real projects, though I've made a Lisp interpreter. I say this so you realize I'm not a complete newbie when it comes to Lisps in general, though I'm definitely a newbie to Clojure(Script).

micha 23:29:33

@mhr: well hoplon is way simpler than any of the react things, i think

micha 23:30:16

so if you're new to clojurescript and you're not already a react expert i think you'll probably benefit from at least prototyping something in hoplon

micha 23:31:25

hoplon has far less tooling and configuration to get things going also

micha 23:31:48

and the conceptual model is very small and simple

mhr 23:34:18

that's great to hear! I'll definitely start with hoplon and see where that takes me, then. do you have any experience with using hoplon with datascript? how hard would interop be?

micha 23:34:48

thre has been a lot of work there, no issues really

mhr 23:35:02

cool

micha 23:36:37

maybe nooe of the datascript users are in here at the moment

mhr 23:37:08

I could ask on #datascript

mhr 23:37:15

about using hoplon with it

micha 23:37:38

oh yeah

mhr 23:37:41

if possible, I still would like to know how the reagent/react model differs from hoplon, if only for my own intellectual curiosity

micha 23:38:06

hm ok

micha 23:38:24

i can do a hand waving type of explanation

mhr 23:38:35

that would be useful

micha 23:38:39

haha ok

micha 23:39:07

so the idea behind react is to emulate an immediate mode rendering surface on top of the DOM

mhr 23:39:27

virtual dom, I have heard of that

micha 23:39:34

that is to say make the DOM stateless, like pixels on a screen

micha 23:39:37

yeah

micha 23:40:19

so the pattern with react is appealing because you make clientside applications (applications that run in js in the client) that are organized the way you'd make a php application or something

micha 23:40:46

in php you have a very simple request -> generate html -> render new page

micha 23:41:13

each request results in loading new html that was generated by php program in the server

micha 23:41:27

the php program is usually stateless

micha 23:41:43

so this is nice in a lot of ways, easy to think about, and so on

micha 23:42:14

now react makes this happen in the client by using a virtual dom

micha 23:42:35

the user interacts with something in the page, triggering an event handler

micha 23:42:54

the event handler makes a "request" to the "php backend"

micha 23:43:02

the "request" here is a function in the client

micha 23:43:19

and the "php backend" is the clientside code and state

micha 23:43:41

anyway the function that the handler calls is like the rendering function

micha 23:44:11

when you call it with parameters it blows away the html int he DOM and generates a new DOM from the html generated by the rendering function

micha 23:44:22

just like how you'd do it with php

micha 23:45:10

but if you actually blew away the entire DOM and recreated it every time anything changed, it would be a lot of work

micha 23:45:14

very slow

mhr 23:45:27

ah so diffing comes in

micha 23:45:32

so they do the virtual dom thing, where they make a diff and apply a patch

micha 23:45:34

yeah

mhr 23:45:42

I'm familiar with some of these terms from soaking it up on HN

mhr 23:45:50

but I didn't know how they fitted together

mhr 23:45:52

cool!

micha 23:46:04

sweet

micha 23:46:12

ok so how you'd use this with react is like this

micha 23:46:31

you might have a single mutable variable, an "atom" in cljs

micha 23:47:06

any mutable thing in clojure will generally implement these interfaces

micha 23:47:28

one of which is that you can add a callback to be called whenever the value in the mutable var changes

micha 23:47:46

it will be called with two arguments, the previous and next values of the var

micha 23:48:11

so what you'd do is keep the state of the client in a single atom

micha 23:48:40

and your render function generates the html (as data) from that, as a pure function

micha 23:49:07

given the contents of the atom, it returns some representation of the html you want in the dom

micha 23:49:16

could be a string, whatever

micha 23:49:30

then react takes that and does the diffing and so on

micha 23:49:34

and updates the actual dom

micha 23:50:14

so now if you update that atom, the dom is rerendered according to your render function

micha 23:50:37

so this all sounds pretty amazing

micha 23:50:57

tracking so far?

mhr 23:51:23

yes

micha 23:51:33

ok cool

micha 23:51:49

so there are a lot of issues that crop up once you start making a real application

micha 23:51:53

naturally

mhr 23:51:54

thank you by the way

micha 23:52:04

i mean we don't use php to make these things for a reason

micha 23:52:14

like the php way seems so simple at first

micha 23:52:25

but pretty soon you need sessions, and redirects, and so on

micha 23:52:29

and flash messages

micha 23:52:37

pretty soon you're in the weeds

micha 23:52:54

imho the react way gets you right back there

micha 23:53:06

with janky ways you need to manage mutable state

micha 23:53:39

because just like with the stateless php server model, it's very difficult to handle mutable state in that world where everything is nice and stateless

micha 23:54:02

also diffing is a difficult thing to do efficiently

micha 23:54:11

some things are np complete, etc

micha 23:54:17

like diffing a sequence of things

micha 23:54:48

so you need to explicitly add hints so the differ can know more about the dom

micha 23:55:03

like adding unique ids to elements so the differ can track them

micha 23:55:06

things like that

micha 23:55:28

additionally, treating the dom as a buffer you can just write to is an api inversion

micha 23:55:35

it's not really a buffer at all

micha 23:56:02

like a framebuffer or console, yes, because you can just overwrite things sequentially, and the things themselves are stateless

micha 23:56:16

like a pixel does not contain state, it simply has a value

micha 23:56:25

but the dom isn't like that

micha 23:56:35

dom elements are objects with their own states

micha 23:57:21

some of it is even computed by the browser itself (like the width of the element, for example, which is a property of the element, but it can change at any time when the browser decisdes to adjust the layout)

micha 23:57:48

so the dom is not an immediate mode rendering surface, it's a retained mode one

micha 23:58:13

and building an immediate mode thing on top of a retained mode thing is an api inversion, and that never works out well

micha 23:58:25

like building a low level language on top of a high level one

micha 23:58:39

you can't have a non-leaky abstraction when you do that

micha 23:58:57

the leaks you see in the react things is the stuff like the unique indexes you need

micha 23:59:04

and the lifecycle protocols

micha 23:59:14

like IWillMount and IDidMount and so on

mhr 23:59:24

huh

micha 23:59:33

callbacks that are called when an element is put into the dom (because you don't control that)

micha 23:59:48

or when it's removed, or before it's removed, or before it goes in, etc etc

micha 23:59:52

there are a ton of them

micha 00:00:05

because react controls allocating dom elements

micha 00:00:19

and those elements may contain their own state

micha 00:00:28

so you may need to override somet hings

micha 00:00:36

so you need callbacks

micha 00:00:48

like run this callback when you put this thing into the dom

micha 00:00:53

run that one when you remove it

micha 00:00:54

etc

micha 00:01:07

but of course once you have those you need before-before-remove

micha 00:01:15

and before-after-before-remove

micha 00:01:17

and so on

micha 00:01:42

anyway that's react

micha 00:01:48

hoplon is the opposite of all that

micha 00:02:03

hoplon doesn't try to make the php model work

mhr 00:02:19

the clojure wrappers don't have means to cope with react's faults?

micha 00:02:20

hoplon leverages the fact that dom elements are stateful

mhr 00:02:24

*clojurescript

micha 00:02:31

no, it's a fault in the model really

mhr 00:02:37

I see

micha 00:03:04

whenever you have an api inversion you'll end up with lots of complexity when you start using it for real

micha 00:03:20

like imagine compiling to ASM that's implemented in JS

mhr 00:03:30

yeah

mhr 00:03:32

leaky

micha 00:03:39

you'll need all kinds of escape hatches to make it not be uselessly slow

micha 00:03:57

those protocols and whatnot are the escape hatches

mhr 00:04:22

do go on about hoplon's approach

micha 00:04:31

hoplon takes the opposite approach

micha 00:04:38

in hoplon there is no rendering

micha 00:04:50

you just make dom elements like normal

micha 00:05:18

so like (div "hi world") in hoplon is exactly the same as

micha 00:06:06

var a = document.createElement("DIV"); a.appendChild(new TextNode("hi world")); return a;

micha 00:06:35

that's not a virtual div, or some cljs type, it's a regular div

micha 00:06:56

you can make a whole DOM too:

micha 00:07:23

``` (page "index.html")

(html (body (h1 "hi world"))) ```

micha 00:07:33

that's a valid hoplon application

micha 00:07:50

you can compile that and you will get a html file (index.html) and a main.js file

micha 00:07:56

which it will automatically load etc

micha 00:08:23

notice how div for example is a function

micha 00:08:58

(div :id "unit1" (span "hello") (span "world"))

micha 00:09:04

^^ attributes

micha 00:09:15

and children

micha 00:09:27

you can construct your own custom elements

mhr 00:10:03

so when the model changes, hoplon deals with that by using javelin instead of the analogous diffing

mhr 00:10:05

?

micha 00:10:10

(defelem my-list [attributes children] (ul attributes (map li children)))

micha 00:10:20

yes so consider:

micha 00:11:48

``` (page "index.html")

(def clicks (cell 0))

(html (body (button :click (fn [event] (swap! clicks inc)) (text "You clicked ~{clicks} times!")))) ```

micha 00:12:03

that's a program with some state and behavior

mhr 00:12:21

oh, that's nice

micha 00:12:31

the :click attribute is used to add a handler for the onclick event

micha 00:12:57

if you pass a function as the value of an attribute it's unambiguous: you want to add a event handler

micha 00:13:15

there is no other useful way to interpret function values as attributes of an element, right?

mhr 00:13:38

true

micha 00:13:39

so :click (fn [event] ...) sets a onclick handler on the button

micha 00:13:57

then the (text ...) macro does interpolation and creates a reactive TextNode

micha 00:14:08

whenever clicks changes that text node will change too

micha 00:14:25

but only the text value of the TextNode object

micha 00:14:42

when clicks changes the TextNode itself is not destroyed and recreated or anything

micha 00:15:04

only the textContent or whatever property of the object is updated in place

micha 00:15:09

automatically of course

micha 00:15:22

equivalent way to do that:

micha 00:16:12

``` (page "index.html")

(def clicks (cell 0))

(html (body (button :click (fn [event] (swap! clicks inc)) :text (cell= (str "You clicked " clicks " times!"))))) ```

micha 00:16:16

last line

micha 00:16:26

cell= makes a formula cell

micha 00:16:38

just like in a spreadsheet

micha 00:16:57

and the :text attribute sets the text content of the element

micha 00:17:13

whenever the formula changes, the text content of the element is updated in place

micha 00:17:42

there are a few things to notice

micha 00:18:03

first, this looks kind of like a normal computer program, not some wacky special webapp

micha 00:18:17

like any computer program ever made has this kind of format

micha 00:18:41

like pull in library code, declare module at top

micha 00:18:57

then initialize state after that, set things up, define things

micha 00:19:08

then finally you start your IO loop

micha 00:19:30

where you read input, do work, and maybe wait for more input if you're making an interactive program

micha 00:19:44

the IO loop is the (html ...

micha 00:19:46

the dom

micha 00:20:07

the dom you create there with that (html ... expression

mhr 00:20:31

how would that snippet look different if it was in Reagent/re-frame?

micha 00:20:42

so you can basically use all the normal development concepts that apply to normal computer programs

micha 00:21:23

i'm too rusty with that to do it justice

mhr 00:21:35

okay

mhr 00:21:45

I'll look up examples and compare

micha 00:21:45

but another thing to see in the hoplon one is that we leverage static allocation

micha 00:22:03

like the dom elements that are allocated are allocated almost declaratively

micha 00:22:37

static allocation means that the "lifecycle" of an element is very simple

micha 00:23:03

if you want to do something before some element is created, you just put the expression that does the thing above the expression that creates the element

micha 00:23:09

like a normal lisp program

micha 00:23:16

there is no need for lifecycle protocols

mhr 00:23:38

very cool

micha 00:23:53

more tricky is when static allocation is impossible

micha 00:24:09

like if you have N things, but you don't know what N is at compile time

micha 00:24:22

like a table of data you want to display

micha 00:24:30

and you don't know how many rows there will be

micha 00:24:33

things like that

mhr 00:25:01

yeah

micha 00:25:11

so we developed some macros to help with that, to preserve the benefits of static allocation, but also allow for dynamically adding things at runtime

mhr 00:25:19

I've had to deal with table libraries like that in the past in JS

mhr 00:26:31

can you give an example of the metaprogrammed "dynamic" allocation?

micha 00:26:43

``` (def things (cell ["one" "two"])

(html (body (ul (for-tpl [thing things] (li :text thing))))) ```

micha 00:27:06

so that would result in a <ul> with two <li> children

mhr 00:27:19

for-tpl is the macro?

micha 00:27:24

yes

micha 00:27:51

there is a corresponding regular function, but the macro does some nice syntax sugaring to make it look nice

micha 00:28:27

so that's like a for loop, it assigns thing to a javelin cell that contains the value of the nth item in things

mhr 00:28:53

if I found myself in a similar situation, would making a macro like for-tpl to get around it be difficult?

micha 00:29:28

macros are useful when there is boilerplate code that can be emitted at compile time

micha 00:29:37

the macros can eliminate the boilerplate

mhr 00:29:42

right

mhr 00:30:20

what happens if you want to have the number of list items be determined by a cell?

micha 00:30:20

there are a lot of subtle issues that for-tpl solves

micha 00:30:38

the number of items is determined by a cell in the example above

micha 00:30:48

things is a cell that contains a number of items

mhr 00:30:54

oh I didn't catch that

mhr 00:31:13

neat

micha 00:31:14

the interesting thing is what happens if you add a new thing, or remove a thing from the things vector

micha 00:31:42

suppose you remove the last thing, so it looks like ["one"] instead of ["one" "two"]

micha 00:31:56

you will see the dom update, the second <li> will disappear

micha 00:32:05

but it's not destroyed

mhr 00:32:08

huh

micha 00:32:19

it is simply moved into a pool

micha 00:32:37

so it's not in the DOM anymore, but it's still available

micha 00:32:48

so now suppse you add an item back to the things

mhr 00:32:54

aha, sorta virtualized as in react

mhr 00:33:02

but on destruction instead of prior to creation

micha 00:33:02

like ["one"] -> ["one" "three"]

micha 00:33:20

now the for-tpl thing will realize that it needs to allocate a new thing

micha 00:33:32

and it also sees that there is an element in the pool it can use

micha 00:33:43

so it doesn't create a new object, it just puts that one back in the dom

micha 00:33:51

now this is probably the most important part:

micha 00:34:02

that element that was taken out and then put back in

micha 00:34:09

it's already automatically in the correct state!

micha 00:34:48

remember the for-tpl made that thing cell that contains the value of the nth item in things right?

mhr 00:34:55

yes

micha 00:35:11

and when the <li> was created, we bound its :text attribute to thing

micha 00:35:29

so now that we added a second item back to the things vector

micha 00:35:40

the text of the element in the pool automatically updated!

micha 00:35:45

just like you'd want

micha 00:35:57

and it goes back in the dom and all looks as you expect

micha 00:36:36

we took great pains to make managing state robust, simple, and correct

mhr 00:37:00

I'm slowly realizing that you must be involved in the development of hoplon haha

micha 00:37:23

basically if you can avoid deallocating and reallocating elements, you can avoid 99% of the complexities of managing state

micha 00:37:31

haha yes

micha 00:37:46

i should have made a biased answer disclaimer

mhr 00:38:25

nah

mhr 00:38:44

you provided an objective explanation of the benefits

mhr 00:39:00

even if it came from biased motivations

mhr 00:40:09

why do you call Javelin's paradigm dataflow instead of FRP? what's the difference?

micha 00:40:36

frp is a much bigger thing than javelin

micha 00:41:26

we did a bunch of research into frp and decided we only needed a small set of features

micha 00:42:01

like for instance you won't want to implement fizzbuzz in javelin cells

micha 00:42:21

javelin is just to route data around

micha 00:42:37

like a spreadsheet

micha 00:43:36

frp kind of tries to do everything

mhr 00:44:03

okay I see

mhr 00:44:57

alright, I'm fairly sold on hoplon at this point

mhr 00:45:42

but since you've been listing all the benefits and cool aspects of hoplon, could you enumerate on what you don't like / hate / would like to change about it?

micha 00:46:18

one thing that you need is mobile app and so on

micha 00:46:46

hoplon doesn't have as much there as say react native

mhr 00:47:21

that's far less important to me than desktop and web, and electron ought to work just fine with hoplon since it works with clojurescript well, I imagine

micha 00:47:22

but i have made apps for Android and iOS using like trigger. io

micha 00:48:33

also hoplon isn't really great for brochure type sites

mhr 00:48:45

right

mhr 00:48:52

but neither would react

micha 00:48:58

because load time is not as fast

micha 00:49:12

react can do more optimizations the

mhr 00:49:24

hmm

micha 00:49:25

like prerendering the html

micha 00:49:49

because the dom is still separate in react

micha 00:50:07

it's not part of your program like it is in hoplon

mhr 00:50:47

perhaps you guys could prerender as an optimization at the start of an app. does that make sense?

micha 00:51:13

yes we do that like for search engines

micha 00:51:32

but it doesn't help the js load faster

micha 00:51:58

and in hoplon the application is a js program completely

micha 00:52:38

like your program must create the Dom

mhr 00:52:42

right

mhr 00:53:12

but could you not run that js server-side in Node with default values in the cells?

micha 00:53:15

so that greatly simplifies managing state which is great

micha 00:53:53

the problem is that those functions that create the Dom can have state

micha 00:54:21

you can't just scrape it and capture all the state

mhr 00:54:57

okay

micha 00:54:58

because some state is encapsulated or hidden

micha 00:55:08

that's a good thing though

micha 00:55:29

encapsulation is how we make large programs be same

micha 00:55:36

i mean sane

mhr 00:55:45

how bad of a performance hit is there with hoplon for booting an app?

micha 00:56:12

not sure

micha 00:56:27

probably less every year

micha 00:56:39

as devices get faster

mhr 00:56:46

but right now

micha 00:56:49

hoplon isn't getting any slower

micha 00:57:11

you probably need to try it

micha 00:57:21

i don't have recent numbers

mhr 00:57:28

okay

micha 00:58:20

especially if you're new to all this

mhr 00:58:28

are devs generally happy with hoplon's performance from the feedback you guys get?

micha 00:58:43

i think prototyping in hoplon will give you a good start

mhr 00:58:47

I'm definitely going to give it a try, I'm just asking

micha 00:59:05

yeah we've never really needed to optimize it

micha 00:59:28

because that's never been the bottleneck in any real application

mhr 00:59:37

okay cool

micha 00:59:49

the bottleneck will be the backbend etc

micha 01:00:43

performance in the Dom, even for very large apps is extremely good

micha 01:02:00

hoplon never updates anything but the minimum number of changes

micha 01:02:20

because of the way javelin cells work

micha 01:02:46

a cell only updates if a cell it depends on changes its value

micha 01:03:03

it's a sort of diff

micha 01:03:17

but the inverse of how react does it

mhr 01:03:41

push-based dataflow

micha 01:03:54

yes, unidirectional also

micha 01:04:14

another difference from frp

mhr 01:04:26

FRP is bidirectional? I wasn't aware of that

mhr 01:04:35

or you mean FRP is pull-based

micha 01:04:39

it's everything

micha 01:04:43

both

mhr 01:04:57

I don't understand what you mean by that

micha 01:05:20

i guess frp has become a vague term

micha 01:05:41

but like kenny tilsons Cell stuff

micha 01:05:51

that's frp

micha 01:05:57

it has lazy cells

mhr 01:06:01

my understanding of FRP has long been of dataflow with pure functions operating on reactive values

micha 01:06:02

that's pull

mhr 01:06:19

though which direction I've been hazy on

micha 01:06:41

also javelin formulas need not be pure

micha 01:07:02

which is a huge simplification in practice

micha 01:07:42

like a javelin cell can keep track of some internal state

micha 01:08:07

so you can encapsulate that state right where it's needed

micha 01:08:34

without needing to introduce new primitives to shim it into the model

micha 01:09:36

javelin doesn't have any laziness

micha 01:09:50

it's eagerly updating

micha 01:10:21

but it prunes updates from the graph if their dependencies haven't changed

mhr 01:10:33

hypothetically speaking, what would be the benefit of it being lazy?

micha 01:11:08

a lazy formula need not update at all until something requests it's value

micha 01:11:21

but then it must be pure

micha 01:12:14

also it's hard for me to see the difference between a lazy formula and a regular function

mhr 01:12:27

I see

micha 01:12:47

so we just use functions if we want pull semantics

micha 01:15:39

there is a repo on github with lots of hoplon demo apps

micha 01:18:00

and everyone is friendly and helpful in here if you get stuck on something :D

mhr 01:20:37

yeah I can tell!

mhr 01:20:56

thanks for spending so much time explaining everything!

micha 01:21:24

np! have fun!