Fork me on GitHub
#hoplon
<
2016-06-02
>
escherize03:06:44

Hi, I'm looking to move some parts of my rfp-based cljs app into clojure files, and I was investigating javelin-clj. I noticed https://github.com/tailrecursion/javelin-clj is 404'ing so I wanted to know: where is javelin-clj code/documentation?

escherize03:06:52

or is it just a part of javelin?

dm310:06:29

I don't think javelin-clj even works

dm310:06:46

there's a branch there that was supposed to do something with it

escherize10:06:56

Looks like I should bet on this

dm310:06:36

from THE Lisp cell guy

escherize10:06:01

wow thats a disjointed readme file

micha11:06:42

@escherize what kind of application is it?

escherize11:06:14

I'm moving some re-frame handlers into cljc for better tooling/UX/testing 🙂

escherize11:06:32

and so I'm trying to emulate re-frame's dispatch and subscribe mechanisms.

micha11:06:02

the benefits of frp on the backed are not clear to me at all

micha11:06:46

especially when you have distributed systems that can fail and restart and stuff

escherize11:06:34

Yeah, I'm more interested in testing the behavior of the frontend as a sequence of pure functions on a db atom, and subscriptions (like lenses) onto the atom which assert that they see the correct thing.

escherize11:06:43

on second thought I'm not sure that requires any frp stuff at all...

escherize11:06:04

I think... I could pull it off with just atoms.

micha11:06:10

lol yep i always end up back at functions

escherize11:06:21

That's the beauty of boot

mhr20:06: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).

micha20:06:33

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

micha20:06: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

micha20:06:25

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

micha20:06:48

and the conceptual model is very small and simple

mhr20:06: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?

micha20:06:48

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

micha20:06:37

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

mhr20:06:08

I could ask on #C07V8N22C

mhr20:06:15

about using hoplon with it

mhr20:06:41

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

micha20:06:24

i can do a hand waving type of explanation

mhr20:06:35

that would be useful

micha20:06:07

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

mhr20:06:27

virtual dom, I have heard of that

micha20:06:34

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

micha20:06: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

micha20:06:46

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

micha20:06:13

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

micha20:06:27

the php program is usually stateless

micha20:06:43

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

micha20:06:14

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

micha20:06:35

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

micha20:06:54

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

micha20:06:02

the "request" here is a function in the client

micha20:06:19

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

micha20:06:41

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

micha20:06: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

micha20:06:22

just like how you'd do it with php

micha20:06:10

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

micha20:06:14

very slow

mhr20:06:27

ah so diffing comes in

micha20:06:32

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

mhr20:06:42

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

mhr20:06:50

but I didn't know how they fitted together

micha20:06:12

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

micha20:06:31

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

micha20:06:06

any mutable thing in clojure will generally implement these interfaces

micha20:06:28

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

micha20:06:46

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

micha20:06:11

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

micha20:06:40

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

micha20:06:07

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

micha20:06:16

could be a string, whatever

micha20:06:30

then react takes that and does the diffing and so on

micha20:06:34

and updates the actual dom

micha20:06:14

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

micha20:06:37

so this all sounds pretty amazing

micha20:06:57

tracking so far?

micha20:06:49

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

micha20:06:53

naturally

mhr20:06:54

thank you by the way

micha20:06:04

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

micha20:06:14

like the php way seems so simple at first

micha20:06:25

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

micha20:06:29

and flash messages

micha20:06:37

pretty soon you're in the weeds

micha20:06:54

imho the react way gets you right back there

micha20:06:06

with janky ways you need to manage mutable state

micha20:06: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

micha20:06:02

also diffing is a difficult thing to do efficiently

micha20:06:11

some things are np complete, etc

micha20:06:17

like diffing a sequence of things

micha20:06:48

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

micha20:06:03

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

micha20:06:06

things like that

micha20:06:28

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

micha20:06:35

it's not really a buffer at all

micha20:06:02

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

micha20:06:16

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

micha20:06:25

but the dom isn't like that

micha20:06:35

dom elements are objects with their own states

micha20:06: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)

micha20:06:48

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

micha20:06:13

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

micha20:06:25

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

micha20:06:39

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

micha20:06:57

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

micha20:06:04

and the lifecycle protocols

micha20:06:14

like IWillMount and IDidMount and so on

micha20:06:33

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

micha20:06:48

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

micha20:06:52

there are a ton of them

micha21:06:05

because react controls allocating dom elements

micha21:06:19

and those elements may contain their own state

micha21:06:28

so you may need to override somet hings

micha21:06:36

so you need callbacks

micha21:06:48

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

micha21:06:53

run that one when you remove it

micha21:06:07

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

micha21:06:15

and before-after-before-remove

micha21:06:17

and so on

micha21:06:42

anyway that's react

micha21:06:48

hoplon is the opposite of all that

micha21:06:03

hoplon doesn't try to make the php model work

mhr21:06:19

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

micha21:06:20

hoplon leverages the fact that dom elements are stateful

mhr21:06:24

*clojurescript

micha21:06:31

no, it's a fault in the model really

micha21:06:04

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

micha21:06:20

like imagine compiling to ASM that's implemented in JS

micha21:06:39

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

micha21:06:57

those protocols and whatnot are the escape hatches

mhr21:06:22

do go on about hoplon's approach

micha21:06:31

hoplon takes the opposite approach

micha21:06:38

in hoplon there is no rendering

micha21:06:50

you just make dom elements like normal

micha21:06:18

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

micha21:06:06

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

micha21:06:35

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

micha21:06:56

you can make a whole DOM too:

micha21:06:23

(page "index.html")

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

micha21:06:33

that's a valid hoplon application

micha21:06:50

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

micha21:06:56

which it will automatically load etc

micha21:06:23

notice how div for example is a function

micha21:06:58

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

micha21:06:04

^^ attributes

micha21:06:15

and children

micha21:06:27

you can construct your own custom elements

mhr21:06:03

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

micha21:06:10

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

micha21:06:20

yes so consider:

micha21:06:48

(page "index.html")

(def clicks (cell 0))

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

micha21:06:03

that's a program with some state and behavior

mhr21:06:21

oh, that's nice

micha21:06:31

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

micha21:06:57

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

micha21:06:15

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

micha21:06:39

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

micha21:06:57

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

micha21:06:08

whenever clicks changes that text node will change too

micha21:06:25

but only the text value of the TextNode object

micha21:06:42

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

micha21:06:04

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

micha21:06:09

automatically of course

micha21:06:22

equivalent way to do that:

micha21:06:12

(page "index.html")

(def clicks (cell 0))

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

micha21:06:16

last line

micha21:06:26

cell= makes a formula cell

micha21:06:38

just like in a spreadsheet

micha21:06:57

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

micha21:06:13

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

micha21:06:42

there are a few things to notice

micha21:06:03

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

micha21:06:17

like any computer program ever made has this kind of format

micha21:06:41

like pull in library code, declare module at top

micha21:06:57

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

micha21:06:08

then finally you start your IO loop

micha21:06:30

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

micha21:06:44

the IO loop is the (html ...

micha21:06:07

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

mhr21:06:31

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

micha21:06:42

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

micha21:06:23

i'm too rusty with that to do it justice

mhr21:06:45

I'll look up examples and compare

micha21:06:45

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

micha21:06:03

like the dom elements that are allocated are allocated almost declaratively

micha21:06:37

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

micha21:06: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

micha21:06:09

like a normal lisp program

micha21:06:16

there is no need for lifecycle protocols

mhr21:06:38

very cool

micha21:06:53

more tricky is when static allocation is impossible

micha21:06:09

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

micha21:06:22

like a table of data you want to display

micha21:06:30

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

micha21:06:33

things like that

micha21:06: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

mhr21:06:19

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

mhr21:06:31

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

micha21:06:43

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

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

micha21:06:06

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

mhr21:06:19

for-tpl is the macro?

micha21:06:51

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

micha21:06: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

mhr21:06:53

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

micha21:06:28

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

micha21:06:37

the macros can eliminate the boilerplate

mhr21:06:20

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

micha21:06:20

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

micha21:06:38

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

micha21:06:48

things is a cell that contains a number of items

mhr21:06:54

oh I didn't catch that

micha21:06:14

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

micha21:06:42

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

micha21:06:56

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

micha21:06:05

but it's not destroyed

micha21:06:19

it is simply moved into a pool

micha21:06:37

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

micha21:06:48

so now suppse you add an item back to the things

mhr21:06:54

aha, sorta virtualized as in react

mhr21:06:02

but on destruction instead of prior to creation

micha21:06:02

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

micha21:06:20

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

micha21:06:32

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

micha21:06:43

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

micha21:06:51

now this is probably the most important part:

micha21:06:02

that element that was taken out and then put back in

micha21:06:09

it's already automatically in the correct state!

micha21:06:48

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

micha21:06:11

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

micha21:06:29

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

micha21:06:40

the text of the element in the pool automatically updated!

micha21:06:45

just like you'd want

micha21:06:57

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

micha21:06:36

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

mhr21:06:00

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

micha21:06:23

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

micha21:06:46

i should have made a biased answer disclaimer

mhr21:06:44

you provided an objective explanation of the benefits

mhr21:06:00

even if it came from biased motivations

mhr21:06:09

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

micha21:06:36

frp is a much bigger thing than javelin

micha21:06:26

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

micha21:06:01

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

micha21:06:21

javelin is just to route data around

micha21:06:37

like a spreadsheet

micha21:06:36

frp kind of tries to do everything

mhr21:06:03

okay I see

mhr21:06:57

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

mhr21:06: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?

micha21:06:18

one thing that you need is mobile app and so on

micha21:06:46

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

mhr21:06: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

micha21:06:22

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

micha21:06:33

also hoplon isn't really great for brochure type sites

mhr21:06:52

but neither would react

micha21:06:58

because load time is not as fast

micha21:06:12

react can do more optimizations the

micha21:06:25

like prerendering the html

micha21:06:49

because the dom is still separate in react

micha21:06:07

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

mhr21:06:47

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

micha21:06:13

yes we do that like for search engines

micha21:06:32

but it doesn't help the js load faster

micha21:06:58

and in hoplon the application is a js program completely

micha21:06:38

like your program must create the Dom

mhr21:06:12

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

micha21:06:15

so that greatly simplifies managing state which is great

micha21:06:53

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

micha21:06:21

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

micha21:06:58

because some state is encapsulated or hidden

micha21:06:08

that's a good thing though

micha21:06:29

encapsulation is how we make large programs be same

micha21:06:36

i mean sane

mhr21:06:45

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

micha21:06:27

probably less every year

micha21:06:39

as devices get faster

mhr21:06:46

but right now

micha21:06:49

hoplon isn't getting any slower

micha21:06:11

you probably need to try it

micha21:06:21

i don't have recent numbers

micha21:06:20

especially if you're new to all this

mhr21:06:28

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

micha21:06:43

i think prototyping in hoplon will give you a good start

mhr21:06:47

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

micha21:06:05

yeah we've never really needed to optimize it

micha21:06:28

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

mhr21:06:37

okay cool

micha21:06:49

the bottleneck will be the backbend etc

micha22:06:43

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

micha22:06:00

hoplon never updates anything but the minimum number of changes

micha22:06:20

because of the way javelin cells work

micha22:06:46

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

micha22:06:03

it's a sort of diff

micha22:06:17

but the inverse of how react does it

mhr22:06:41

push-based dataflow

micha22:06:54

yes, unidirectional also

micha22:06:14

another difference from frp

mhr22:06:26

FRP is bidirectional? I wasn't aware of that

mhr22:06:35

or you mean FRP is pull-based

micha22:06:39

it's everything

mhr22:06:57

I don't understand what you mean by that

micha22:06:20

i guess frp has become a vague term

micha22:06:41

but like kenny tilsons Cell stuff

micha22:06:51

that's frp

micha22:06:57

it has lazy cells

mhr22:06:01

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

micha22:06:02

that's pull

mhr22:06:19

though which direction I've been hazy on

micha22:06:41

also javelin formulas need not be pure

micha22:06:02

which is a huge simplification in practice

micha22:06:42

like a javelin cell can keep track of some internal state

micha22:06:07

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

micha22:06:34

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

micha22:06:36

javelin doesn't have any laziness

micha22:06:50

it's eagerly updating

micha22:06:21

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

mhr22:06:33

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

micha22:06:08

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

micha22:06:21

but then it must be pure

micha22:06:14

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

micha22:06:47

so we just use functions if we want pull semantics

micha22:06:39

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

micha22:06:00

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

mhr22:06:37

yeah I can tell!

mhr22:06:56

thanks for spending so much time explaining everything!

micha22:06:24

np! have fun!