Create a gist now

Instantly share code, notes, and snippets.

@t0yv0 / Secret
Last active Aug 29, 2015

[19:20] panesofglass So, virtual DOM

[19:20] panesofglass Did you say that formlets already does that?

[19:20] t0yv0 since i don't know exactly what vdom is

[19:20] t0yv0 i can't tell for sure

[19:21] t0yv0 but i know that formlets do use efficient dispatch of changes in a tree of widgets

[19:21] t0yv0 some application of "derivative" types, d/Tree = type Delta ..

[19:22] panesofglass General idea is that you have a bunch of DOM nodes created in the browser.

[19:23] panesofglass Using string templates and innerHTML is about the worst, perf wise, right?

[19:23] t0yv0 yeah, well first of all, let's lay performance aside for the moment :)

[19:23] panesofglass So, DOM templates have been all the rage, but they still generally replace large chunks of the DOM

[19:23] t0yv0 talking correctness, how does VDom manage identity? does it?

[19:24] t0yv0 there's a sweet fragment of HTML where identity does not matter

[19:24] panesofglass The idea of the virtual DOM is to use a lightweight representation of the DOM in memory and perform a diff to determine exactly what needs to change, then make only those few, select changes, if any.

[19:24] t0yv0 there it's easy to see how this would work

[19:24] t0yv0 but how do you do a diff once there're some opaque (exists state, .. ) pieces?

[19:25] t0yv0 also

[19:25] panesofglass I don't think it manages identity. Looking at mithril, I think it treats DOM nodes as values and does a structural comparison

[19:25] t0yv0 in, say, formlets, you don't need to perform any diff because you know what needs to change in the first place

[19:25] t0yv0 yeah

[19:25] t0yv0 if it doesn't manage identity, i think it's a mistake, or rather

[19:25] panesofglass are you saying that the whole content block in which a formlet renders is replaced, or that, say, a single attribute was all that needed to get updated, so you only modified that one attribute?

[19:26] panesofglass within the formlet dom, that is

[19:26] t0yv0 it's a good idea for a subset but you need MORE than that subset

[19:26] t0yv0 a formlet is actually a tree of sub-formlets

[19:26] panesofglass correct

[19:26] t0yv0 when change happens, only that thing that changed gets redrawn

[19:26] t0yv0 let me lookup the code for you, jas

[19:26] panesofglass

[19:27] t0yv0 don't quote that guy to me, i have taken a dislike :)

[19:27] t0yv0 j/k

[19:28] t0yv0 something along these lines:

[19:28] panesofglass uh oh!

[19:28] t0yv0 i'm not saying it's perfect or even that it's that good

[19:28] t0yv0 but the basic idea of propagating changes to where they matter, this is very doable

[19:28] panesofglass how does Apply work?

[19:29] panesofglass I think this is similar. It walks the DOM applying the necessary changes?

[19:29] panesofglass Is that accurate?

[19:29] t0yv0 see, there's no DOM here

[19:29] t0yv0 :)

[19:29] panesofglass If so, I think that's equivalent.

[19:29] panesofglass (and probably better)

[19:30] t0yv0 when this is used by formlets to render, individual formlets remember their own pieces of dom, and they manage that

[19:30] t0yv0 the Tree type helps dispatch changes to the formlet that matters

[19:30] t0yv0 so the dom update happens locally

[19:30] t0yv0 at least that was the hope

[19:30] panesofglass nice

[19:30] t0yv0 Apply can be thought of giving a meaning to Edit<'T>

[19:31] t0yv0 Edit<'T> is an encoding of a transform of Tree<'T>

[19:31] panesofglass is it possible to use this to roll changes forward and back, e.g. undo/redo?

[19:31] t0yv0 so people say Edit<'T> is a "derivative" type from Tree<'T>

[19:31] panesofglass I see

[19:31] t0yv0 no it's not and here's where the big problem comes in

[19:31] panesofglass By memoizing Edit, I would expect I could get something that would allow me to undo/redo

[19:32] t0yv0 the big problem is, as we found out in practice, one cannot avoid or gloss over managing identity

[19:32] panesofglass Also, is this used for rendering piglets, or can it be used for that purpose?

[19:32] panesofglass what do you mean by identity?

[19:32] panesofglass and why is that important?

[19:32] t0yv0 actually i have no clue, tarmil is your guy on piglets :)

[19:32] t0yv0 so about identity:

[19:33] t0yv0 this is what we found to be most painful in practice, and i am still searching for a good way to deal with it, but here it is

[19:33] t0yv0 most libraries out there give you stateful objects, which is solid idea, it is about abstraction

[19:33] t0yv0 so you get (x : T), where T is an abstract type, and it is mutable

[19:34] panesofglass This sounds like the perfect topic for another of your brilliant blog posts.

[19:34] t0yv0 even if you are not using any frameworks, you already have that with plain browser

[19:34] t0yv0 for example, a simple box has state such as focus

[19:34] t0yv0 something that the user interacts with directly and changes

[19:34] t0yv0 now

[19:34] panesofglass ah

[19:35] t0yv0 if we had a primitive, that for any abstract type T, "serialized" its internal opaque state, and was able to restore it

[19:35] t0yv0 like,

[19:35] panesofglass the idea behind the virtual DOM is to by-pass this issue, at least from the developer's point of view.

[19:35] t0yv0 if everything had toJSON/fromJSON

[19:35] t0yv0 then it would be perfect

[19:35] t0yv0 it would allow this kind of techniques to be really successful

[19:35] t0yv0 however, in the real world, i don't think you CAN bypass the isse

[19:36] panesofglass the idea behind the VDOM is to separate the render state from the event state

[19:36] t0yv0 how?

[19:37] t0yv0 how do you diff a tree of states where some of the states are opaque abstract types?

[19:37] t0yv0 it seems to me it can't be solved in general

[19:37] t0yv0 but maybe i just don't know vdom :)

[19:39] t0yv0 note a similar issue comes up with defining someting like formlet "many" combinator: say you have a logical model 'T, and a renderer that is T -> UI, how do you lift it to a list?

[19:39] panesofglass I look forward to learning. :)

[19:39] t0yv0 more appropriately, you have a Signal<'T>, Signal<'T> -> UI, and how do you make Signal> -> UI ?

[19:39] panesofglass Oh, I see the issue wrt "tree of states"

[19:39] t0yv0 in this kind of combinators, you need a diff, you also need a policy on identity

[19:39] panesofglass React, mithril, etc. render the tree, then do the diff

[19:40] panesofglass then apply the diff results to the actual DOM

[19:40] panesofglass on the requestAnimationFrame cycle

[19:40] t0yv0 this is under-specified. for example, if on step 1 we had items [Apple, Orange], and on step 2 we had [Apple], and on step 3 you have [Apple, Orange] again, okay?

[19:41] panesofglass What's the Signal<'T>?

[19:41] t0yv0 you have a difff-er, and a renderer. now, will your renderer associate identially same DOM nodes to Apple/step1 or Applye/step3?

[19:41] panesofglass Is that an event?

[19:41] t0yv0 i'm talking loosely here, some kind of changeable value

[19:41] panesofglass oh, okay

[19:42] panesofglass ah, I see your point

[19:42] t0yv0 so for example, on reasonable policy would be: Apple gets preserved between steps 1 and 2, so we keep the same DOM tree for it

[19:42] t0yv0 but we remove Orange tree

[19:42] panesofglass I haven't dug deep enough to know the details

[19:42] t0yv0 so we re-create Orange tree again on step 3

[19:42] panesofglass but I think it's a string match

[19:42] t0yv0 but here we have a problem, if Orange tree had been modified by the user and we forgot to capture this change

[19:42] panesofglass maybe

[19:42] panesofglass I really don't know

[19:42] t0yv0 :)

[19:43] t0yv0 sorry, i must be a bit overwhelming

[19:43] t0yv0 i'm just really curious what React would do in such an example

[19:43] panesofglass I think you are right about Apple, Orange -> Apple -> Apple, Orange

[19:43] t0yv0 i think for me the bottom line is, that it might be useful to allow ignoring identity sometimes, but the developer should have access to managing it in general

[19:43] panesofglass The final Orange and the first Orange would not be related

[19:43] panesofglass though they may appear identical

[19:43] t0yv0 y

[19:44] t0yv0 another plausible policy for this combinator would be to memoize and hide these nodes

[19:44] t0yv0 so it saw Orange in step 1, it rendered it; on step 2 it simply hides the rendered stuff; on step 3 it re-appears

[19:44] panesofglass The idea I had from it is that it is similar to snapshots in time

[19:44] t0yv0 y

[19:44] panesofglass You don't care about identity, just the snapshot

[19:45] t0yv0 that'd be awesome

[19:45] t0yv0 just one problem -

[19:45] t0yv0 you can't snapshot dom nodes, can you?

[19:45] panesofglass of course, there are always problems. :)

[19:45] t0yv0 no i mean i would be seriously excited about this if

[19:45] t0yv0 there was a way to snapshot all relevant state from the dom nodes

[19:45] t0yv0 and i actually don't know. is there?

[19:46] t0yv0 we can obviously observe if a textbox has focus or not, and record that

[19:46] t0yv0 does it generalize?

[19:46] t0yv0 another thing is - almost all 3rd party libs WebSharper works with

[19:46] t0yv0 this would not be possible

[19:46] t0yv0 because they by design use abstract types that mutate themselves

[19:46] panesofglass I think because tools like React control everything they manage, they would break if you used, say, jQuery to modify something in the DOM controlled by React

[19:46] t0yv0 definitely

[19:46] panesofglass is that what you are trying to avoid?

[19:47] t0yv0 they'd also not be able to render a jQuery UI widget inside a React widget

[19:47] t0yv0 don't know..

[19:47] panesofglass I don't think focus would be important in this case.

[19:47] panesofglass Just the markup

[19:47] panesofglass but I see what you are saying now

[19:47] panesofglass perhaps that is important

[19:47] panesofglass I don't know

[19:47] t0yv0 to that i can for sure say, no, focus IS imporant

[19:48] t0yv0 you see, we went through these months and months of bugfixing :)

[19:48] panesofglass would be interesting to see how that is managed

[19:48] t0yv0 the hard-learned lesson was that you couldn't (at least at that time) casually treat dom state as purely functional

[19:48] panesofglass Here's the build function in mithril, which is likely a much more limited form of what React does:

[19:48] t0yv0 so we introduced some identity management into formlets, etc

[19:48] panesofglass I believe this is part of where things happen

[19:49] t0yv0 hm,, ugh what code :)

[19:50] panesofglass well, it's javascript, what did you expect? ;)

[19:50] t0yv0 yeah

[19:50] panesofglass glad it's not asm.js :D

[19:50] t0yv0 afaik it doesn't do what i'm talking about --^

[19:50] t0yv0 we have some similar shit in formlets i think

[19:50] t0yv0 the builder for the DOM

[19:50] panesofglass lol

[19:51] t0yv0 that tries to do caching, and this stuff

[19:51] panesofglass interesting

[19:51] t0yv0 it's probably not very good, but the idea is similar

[19:51] panesofglass so you are saying W# already does the DOM diffing

[19:51] t0yv0 i just think in general it does not work

[19:51] t0yv0 we don't do DOM diffing but we do incremental DOM manipulation

[19:51] panesofglass okay

[19:51] t0yv0 think about it, why do you need diffing? you only need diffing if you structure your code with functions that are not really functoins

[19:52] t0yv0 so you have, val f : Model -> VDom

[19:52] panesofglass so you apply only actual, changes to the DOM, not just the re-rendered block of formlet markup?

[19:52] t0yv0 and then you need diff (f m1) (f m2)

[19:52] t0yv0 if you structured your renderer as something with more structure than a function

[19:52] panesofglass brb

[19:52] t0yv0 then you won't need diff

[19:52] t0yv0 ok

[19:55] t0yv0 when you're back, do i have your permission to publish the chat log? thx

[20:23] panesofglass so ... right back was stretching it.

[20:23] panesofglass you have my permission to make me look stupid. I.e., yes. ;)

[20:25] t0yv0 that's still publicity hehe

[20:25] t0yv0 thanks

[20:26] t0yv0 i was just trying to say that diff function is necessary only if you insist on describing a transform from Model type to your VirtualDOM or something as a function

[20:26] t0yv0 if you use more structure, e.g. applicative or something, then the need goes away, e.g. you implicitly do the diff/change propagation in the combinators

[20:26] t0yv0 the syntax gets ugly though, of course

[20:27] t0yv0 i am writing a quick blog post. the other nasty thing is how this interacts with higher order dynamic combinators, especially something like monadic bind

[20:28] t0yv0 there is this nice little language called Elm

[20:28] t0yv0 they do a brave right thing and simply outrule monadic bind, there's some kind of stage separation

[20:29] t0yv0 the author even says in the thesis - hey, Signal (Signal t) -> Signal t has been so notoriously difficult to get tight, that we'll just prohibit it. He then designs a type system where you just can't construct anything like that

[20:29] t0yv0 and everything remains pleasant. it's a nice take on it

[20:29] t0yv0 s/get tight/get right/

// model
type Person = { Name: string; Age: int }
// direct approach:
// applied every time model changes
val Render : Person -> UI
let Render p =
Div [
Div [Text p.Name]
Div [Text (string p.Age)]
// indirect/signal/FRP approach:
type Signal<'T> =
member Map<'R> : ('T -> 'R) -> Signal<'R>
val Embed : Signal<UI> -> UI
// applied only once for all changes in model
val Render : Signal<Person> -> UI
let Render p =
Div [
Div [p.Map(fun p -> Text p.Name) |> Embed]
Div [p.Map(fun p -> Text (string p.Age)) |> Embed]
// VDOM approach:
// lift ('T -> UI) to (Signal<'T> -> UI) automatically
// Problem: requires Diff : UI -> UI -> Delta for efficiency
// Puts constraints on what can go into UI.

This doesn't help me. I don't understand the requirement to lift to Signal<'T> -> UI.

Let's say I've requested the page, and the following content is rendered into an empty DOM node:

Div [
    Div [Text "Ryan"]
    Div [Text "34"]

Now suppose I want to, on a double-click, toggle the contenteditable attribute on the Age <div>. The event triggers another render, which produces:

Div [
    Div [Text "Ryan"]
    Div [ContentEditable "contenteditable"] -< [Text "34"]
<div><div>Ryan</div><div contenteditable="contenteditable">34</div></div>

(Aside: is it possible in WebSharper to just add the contenteditable attribute without setting a value?)

This render is compared to the previous render, so the only change to the DOM at this point is to add the contenteditable. React will generate this UI update (where componentDiv is its top-level element):

componentDiv.childNodes[1].setAttribute('contenteditable', 'contenteditable');

It seems that React simply performs mutations against the DOM, so any other stateful values such as :focus, etc. remain un-affected for nodes that remain in the UI. Obviously, if you remove a node, it will no longer have state within the DOM, but I don't imagine you would want that.

Another thing React does is batch DOM updates so that many, subsequent changes get merged into a single update, I believe on the requestAnimationFrame cycle.

In your example above, it seems that the Signal<'T> would bump value changes for all properties of the 'T, which is not necessary. Also, I don't understand why you note "Problem: requires Diff : UI -> UI -> Delta for efficiency." Why is that a problem? I would expect something else to be responsible for executing the diff and then creating and applying the DOM mutations, not a lifted version of the Render function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment