Could updating the DOM directly instead of the virtual dom get any advantage for elm? (the way it does for svelte)

I am trying to understand more how that would work / why it won’t.

1 Like

Is this similar to http://imba.io/?

If so, there has been a discussion there:

2 Likes

Thanks for linking to the discussion. There might be a bit of similarity in the way imba targets the dom.

Rich Harris (the creator of Svelte) said that “Svelte is a language to describe reactive user interfaces, just like Elm, Imba and a few others…”

1 Like

Interesting perspective. What i actually wanted to ask is whether Elm could get rid of the virtual dom because it seems that strategy works very well for svelte. I was curious if at all no virtual could potentially bring any advantage in terms of simplicity and performance. Thanks!

My understanding is that there is not a major difference in perf based on:

There are a lot of ways to be faster than the big name frameworks, but I don’t think using the general “virtual DOM” approach is the crucial factor.

3 Likes

In a project I’m working on I tried the following approach to do animations: store an animationClock : Float value in the top-level module and pass it down to the views that need it. This allows to not add an onAnimationFrame subscription each time I need an animation, but to use it only once at the top level.

Unfortunately, if I need animations everywhere and pass animationClock to most of the parts of the app, this approach basically makes Html.Lazy useless (because the value changes every frame), and the entire view tree (or most of it) must be recalculated on every frame.

It is still fast – I can’t really notice any performance issues visually. But since the view is recalculated on every frame, I can see high CPU usage all the time. To optimize this, I need to find a way to disable the subscription when no animations are running. It’s not that simple, for example some of the views that use animations are just stateless functions like spinner : Animation.Clock -> Element msg that show a loading spinner that just spins forever. So to disable the subscription I need to somehow know if there are any spinners rendered at the moment (and other views that require an animation).

If we used a similar technique like Svelte use (if I understand it correctly) we could evaluate the application’s view function only once during the compilation and detect which parts of it are static or dynamic, and changes to which parts of the Model affect which parts of the view. In this world there would be no need to do any optimizations in my example above, moreover I think Html.Lazy would not be needed anymore. Of course it’s not an easy task because it’s not possible to implement this on elm/virtual-dom level and it requires changes to the compiler, but would be cool :slight_smile:

3 Likes

Does anyone know if the kind of solution used by Svelte would also have the runtime errors issues that virtual DOM and DOM browser extensions sometimes cause?

Svelte is based on quite restricted templates bound to specific fields on an object, and the updates only work if you use it in a certain way. If you for example use a function in a template, it doesn’t get updated when the state changes: https://svelte.dev/repl/b272ae7c3dee478090a4c89254e4afbd?version=3.6.7

In Elm’s view functions on the other hand you can use anything in the language, and they’re guaranteed to update correctly. So it seems like a totally different beast, and way harder, to make Elm work like Svelte (at least without totally changing how you write programs).

2 Likes

I see. Thank you. I didn’t take the time to understand how Svelte works.

A virtual DOM based program is necessarily slower (more CPU intensive) than a perfect program that touched the DOM directly because the two programs will perform exactly the same changes to the DOM and the virtual DOM based program will also render and diff the DOM. Assuming, of course, that the “perfect” direct DOM program doesn’t burn lots of CPU time on some other form of change tracking…

Where the virtual DOM gained traction is that assuming you don’t have a perfect direct DOM program, then it either updates the DOM less frequently than it should — bugs!!! — or it updates it more frequently than it should — potentially a big performance hit since DOM mutations aren’t or at least historically haven’t been cheap. Not wanting buggy updates, direct DOM approaches biased toward updating more often than necessary and burned performance. Virtual DOM implementations paid for themselves by reducing the cost spent on updating the DOM at the expense of rendering and diffing.

Direct DOM programs claw their way back either indirectly when browsers make DOM manipulations faster thereby reducing the costs of over updating. Or they look for cheaper ways to track the updates that need to be performed, potentially trading off some precision while doing so. Svelte falls into the latter category. By constraining the problem more tightly, Svelte builds a model of what changes to propagate to the DOM that is cheap to maintain and use.

Elm gets a big performance benefit out of the fact that view functions have no side effects and hence the meaning of a program won’t change if they are never called or more practically if they are called only per animation frame. That said, Svelte could probably do its dirtiness tracking and only apply changes on animation frames as well. I don’t know whether they do this.

Elm’s lazy HTML is a way to cutoff branches of the view tree from the render and diff process when their arguments don’t change. Where this works, it can be quite beneficial. On the other hand, if used in places where arguments change frequently or are calculated — e.g., building a record — it adds overhead while achieving little or no reuse. It also has trouble with more complex data flow patterns. For example, if one has a large, expensive to compute layout that depends on the items in the layout (rarely changing) and the view width (rarely changing) and we want to only render the portion that is visible (dependent on the rapidly changing scroll position), there is no good point within the view logic to insert a lazy node to cache the layout computation that won’t have this cache destroyed on scroll events. Instead, one ends up making the layout a part of the model and managing its update there even though it adds no extra information to the model that wasn’t already present and hence undermines efforts to make impossible states impossible.

This is akin to the recent discussion of GC. Precise storage management is hard. Precise DOM updates are hard. We choose to trade some imprecision, some performance, or some capabilities to get a programming model that works “better” in practice. Elm makes one set of choices. Svelte makes another. React makes yet another.

Mark

5 Likes

I think it should be possible to analyze the AST during compile time to determine dynamic parts of view and how they should be updated in case of updates to the model. I’m not saying that’s easy to do, but no need to change the way you write programs.

2 Likes

There was something about that in the news not so long ago, was it in regard to SwiftUI? I’ve never been an Apple developer so did not really register the details, perhaps someone who knows can provide a link?

I think the idea was that there is an side-effect free language for describing UIs, and the compiler is automatically able to infer which parts are static and which dynamic, and use that to optimise the UI construction so that only the dynamic parts ever need to be re-computed.

I also have this book, which gets into the theory of how this kind of thing can be added to functional languages: Two Level Functional Languages

Elm Virtual DOM already performs very well - so from the point of view of effort vs reward in implementing this kind of advanced optimisation you have to wonder if it will ever be worth investing the effort.

2 Likes

Please take a look at my post above about animations. The approach I tried wastes too much CPU power by recomputing/diffing the entire view on every animation frame and makes Html.Lazy useless. I can’t think of another way to optimize it rather than managing state and subscription for every spinner or other animatable view (my idea about disabling the global subscription when no animations are running is not perfect because if I do it the whole view would still be recomputed on every frame during animations). I will probably switch to CSS animations.

1 Like

I took this approach with the one application I wrote that had a significant amount of pure Elm animations in it. It seemed to work out ok, but I arrived at this option just by luck as it was the first way I tried it. It is not the easiest as there is a fair amount of overhead in hooking up all those subscriptions.

I also took that approach because I did not know CSS animations - now that I have some experience with them, I would generally prefer them where possible, and keep the amount of pure Elm animation to a minimum.

But yes, I think your use case is a valid one for this kind of optimisation. It would be pretty sweet if Elm could pull that off - theoretically I think it is entirely possible.

==

It is also worth mentioning as others have noted that pure Elm animations may not perform so well on mobile devices with their weaker CPUs. CSS animations are more likely to have been optimised by the browser implementation and more likely to run smoothly.

2 Likes

well, that’s debatable :slight_smile: I’m not an expert in this field, but I read in the past that JS animations can be as fast or even faster than CSS. Also, CSS transitions/animations have another issue – they store state outside of elm’s model and it can be assigned to a wrong node after diffing. So it’s often necessary to use keyed nodes, and you need to make sure that the keys are unique among “instances” of the same view (like a row of buttons that have mouse over transitions, and buttons can change dynamically). I’d prefer to do animations in elm instead.

2 Likes

Interesting, never thought about that or noticed it. Thank you.

1 Like

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.