I had some fun writing this (probably inconclusive) post about Elm and Svelte. Rich Harris, in Write less code, showcase a small code snippet in Svelte as an example of getting something done writing a small amount of code.
I wrote the same thing in Elm and compared with Svelte using these criteria:
- Code Size
- Performances
- The Architecture
- Declarative vs. Imperative Programming
- Static vs. Dynamic typing
- Data Binding
- Single Source of True
- Compilers
1. Code Size
Elm, predictably, has more code.
The Svelte version
<script>
let a = 1;
let b = 2;
</script>
<input type="number" bind:value={a}>
<input type="number" bind:value={b}>
<p>{a} + {b} = {a + b}</p>
The Elm version
module Main exposing (main)
import Browser
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
init = { a = "1", b = "2" }
type Msg = ChangeA String | ChangeB String
update msg model =
case msg of
ChangeA value -> { model | a = value }
ChangeB value -> { model | b = value }
view model =
div []
[ input [ onInput ChangeA, value model.a, type_ "number"] []
, input [ onInput ChangeB, value model.b, type_ "number"] []
, p []
[ text <| String.join " "
[ model.a
, "+"
, model.b
, "="
, case Maybe.map2 (+) (String.toFloat model.a) (String.toFloat model.b) of
Just value -> String.fromFloat value
Nothing -> "undefined"
]
]
]
main = Browser.sandbox { init = init, view = view, update = update }
Characters (excluding spaces) of code:
Elm.....: 630 characters
Svelte..: 127 characters
(*) I count characters removing all tabs/spaces, coping to the clipboard and then running pbpaste | wc -c
Minified and Zipped
Elm.....: ~27 KB
Svelte..: ~3 KB
Wow! that is almost one order of magnitude bigger.
But wait, if Elm came out with 27 KB from 630 characters of code, I guess it adds extra things that will become handy later.
Let’s analyse the footprint of the Real World Example App (Elm and Svelte):
Elm.....: ~29 KB ( +2 KB)
Svelte..: ~15 KB ( +12 KB)
Yep, Elm increment is smaller than Svelte increment. Will these numbers ever switch? I mean, is there any application where Svelte build is larger than Elm build (without using code splitting)? This is an interesting question for which I have no answer.
If you want to see other cool "less-code" examples you can check the official list. I particularly liked the animation in the todo app.
2. Performances
Unless you crate pages with complex animation, video games or display a lot of data, performances are not an issue in modern hardware/browsers.
But for the cases mentioned above, the performances between Svelte and Elm are in the same class (here and here and here). Svelte interact with the DOM directly while Elm use a Virtual DOM that is optimised, leveraging its purity. You can find interesting conversations about these two approaches here and here.
Theoretically a perfect script that modify the DOM directly has the best performance possible. A system based on a virtual DOM need to do the same thing and, plus, it needs to manage the virtual DOM.
In reality generating a perfect script that could work on a plethora of situation is impossible so this is why in the case of Elm and Svelte, the performance are neck and neck.
This is a comparison of performances between Vanilla Javascript, Svelte, Imba, Elm, Vue, React and Angular. The greener, the better.
3. The Architecture
Elm comes with The Elm Architecture built in. This is an overkill but using Elm (or any other framework, for that matter) for this small app is an overkill anyway. The Elm solution is a skeleton ready to be expanded.
With Elm you are embracing a declarative pure functional language that has immutability, pattern matching, type inference, static types, etc. With its pros and cons.
If you are not familiar with the concept of pure functions, they are functions with “same input, same output” and no side effects.
All Elm functions are like this. That is part of what makes Elm reliable and easy to debug.
I guess Svelte could be written with something similar to The Elm Architecture (does Svelte has “on:input”?).
4. Declarative vs. Imperative Programming
Comparing the two snippets, the Elm version seems having more boilerplate, Elm is leaning more on the “how to do” compared to the straightforward and succinct Svelte version that lean on the “what to do”.
I am all in favour of the declarative approach (“what” instead of “how”), but we need to maintain a good balance between the two, otherwise it becomes magical.
In this small example the two-way data binding is hiding some type coercion that could generate unexpected behaviours.
5. Static vs. Dynamic typing
Dealing with input fields of type number is quite difficult. I had several issues before with them using Vue and React. Compared to them, Svelte does a very good job returning “undefined”. To understand this issue try to type “ee” or anything else that is not a number in a type field of type number. The browser will return an empty string. Svelte does some magic to cope with this issue that is hidden from the developer.
I guess the magic is in this function generated by the compiler:
function to_number(value) {
return value === '' ? undefined : +value;
}
The same issue is taken care in the Elm example just before printing the result into the screen, with this line of code:
Maybe.map2 (+) (String.toFloat model.a) (String.toFloat model.b)
For people not familiar with Elm this line does something like:
“I need the mathematical sum of two floating numbers that are stored as string (string is the natural output of the HTML input fields also when they are type number
). So first I need to convert these two strings into numbers, but the conversions can fail. If any of the two conversions fail, I also want the sum operation to fail.”
The result of that line of code is a Maybe Float
where Maybe
is a representation in Elm for something that can fail. The two possible values of a Maybe Float
type are Just Float
(Yeah! Everything went well, here is your beautiful floating number) or Nothing
(whoops, something bad happened, sorry, no number for you).
In case of no number, we print undefined
on the screen, just to emulate the Svelte example because in reality undefined
in Elm doesn’t exist. And neither null
does.
Javascript Bites
Yes, bites, not bytes, as when the Javascript type system is biting you.
Still related to type, if you change the type of a and b to strings as in
<script>
let a = "1";
let b = "2";
</script>
<input type="number" bind:value={a}>
<input type="number" bind:value={b}>
<p>{a} + {b} = {a + b}</p>
The browser will render it as: “1 + 2 = 12” because “+” in Javascript works with everything, including string (it concatenates them). Svelte is doing some type conversion behind the scenes but in this case the function to_number
didn’t run during the initialization.
This would not be the case in a strictly typed language. If you initialize a
or b
as string the compiler will complain because “+” only accepts numbers.
These issues may get fixed once Svelte will support Typescript.
Types Flexibility
As follow up note, while the Svelte version define a
and b
as type number
, in Elm we defined them as strings:
init = { a = "1", b = "2" }
I decided to use strings as this is what is naturally coming out of HTML. Then I convert them to floating point just before adding them.
If we want instead to store them as numbers, we would have rather used
init = { a = Just 1, b = Just 2 }
To take into consideration possible failures during the string to number conversions.
6. Data Binding
Elm doesn’t have automatic two-way data binding. In this sense Elm is more similar to crude HTML. The Elm code is
input [ onInput ChangeA, value model.a, type_ "number" ] []
That is the analogue in HTML of
<input oninput="...", value=model.a, type="number">
The binding is made with onInput
and value attributes, where "..."
is something that calls the update
function with the message ChangeA
and the model, in pseudo code: update( [ "ChangeA", this.value ], model )
.
In Svelte:
<input type="number" bind:value={a}>
The binding is made with the original bind
: clause.
Again there are pros and cons about both approaches. The Elm way requires more wiring and allows you to modify the flow if necessary. The Svelte approach is simpler and is hiding the flow from you.
7. Single Source of True
In Elm, by design, there is only one “thing” (the model
) that can be modified during the life of the application. In this case we chose a record that contains the two values a and b.
In the Svelte example there are two values that are modified and they are the state at the component level. There are several ways to keep the state in Svelte: Stores
, Context
and Props
. Stores
, that are the way to hold the state outside of components, can be of type writable
, readable
, derived
and custom
.
In Elm, again, the state is only one at the application level. Nothing else can have an independent state.
Events All events in Elm are transformed into messages, In this simple example we used two messages:
type Msg = ChangeA String | ChangeB String
One to update the input field a and one to update the input field b. We could have used only one message for both:
type Msg = Change InputField String
Where InputField
is a custom type or, to be even more generic (but not the best practice in Elm):
type Msg = Change String String
This is not a good practice because in Elm you want the compiler to be as strict as possible so that bugs are intercepted at compile time and not at execution time. Having String there doesn’t allow the compiler to give you an error if, for example, you pass a string that is neither a
or b
.
8. Compilers
Both Elm and Svelte have compilers and both compile to Javascript.
Elm compiler is 26K lines of Haskell while Svelte compiler is 15K lines of Typescript.
Elm compiler generates a large Javascript file that is already bundled with the Elm runtime and ready to go. It has 3 modality: Normal, Debug (Turn on the time-travelling debugger) and Optimize (Turn on optimizations to make code smaller and faster).
Svelte compiler generates a small Javascript file that, in a second step, gets bundled with the Svelte runtime. You can find analysis of the compiled file here and here.
Svelte compiler has several modalities, the most significant are: Server Side Rendering, Development, CSS (To include CSS in the JavaScript and injected it at runtime), Hydratable, Immutable (Tells the compiler that you promise not to mutate any objects), Legacy (to work in IE9 and IE10).
A bit more about RealWorld example and performances
RealWord examples may be outdated or badly implemented so take these observations with a grain of salt.
I ran some simple tests on RealWorld examples but I was not able to notice any relevant difference. On slow network connection the Svelte version gives faster numbers (it also uses code splitting that is not available in Elm) but visually the Elm version render faster. The Svelte version shows the text “Loading…” for a long time, this could be caused by some implementation issues. In the Svelte version the browser is downloading 7 chunks of Javascript in 4 slots and the main part of the page is only downloaded after the fourth slots.
All tests are made with Chrome 79.0.3945.88 on MacBook Pro with “Slow 3G” network.
Elm on the left, Svelte on the right. Performances are similar:
Svelte — Assets are served as four slots for a total of seven chunks. While the fourth chunk is coming the page is still “Loading…”. An implementation issue?
Svelte — The last chunk
Elm —The first (and last) chunk
So, who is the winner? 🏆
We just scratched the surface of these two technologies but can we declare a winner?
Yes, the winner is whoever chooses the right tool for the right task.
This post highlights probably one of the main objective tradeoffs between these two frameworks.
Svelte is keeping you close to HTML/CSS/Javascript while Elm and elm-ui let you depart from them in exchange for several benefits like, for example, no runtime exceptions.
Other concepts like the learning curve, gradual adoption, performances, footprint sizes are all open to debate.
I praise Svelte for bringing interesting new ideas to the front-end engineering world and I will keep experimenting with it. Contamination is a good thing and we should always learn (copy?) from each other.
In the meantime I will keep using Elm as I believe it is the best fit for the applications that I am building. I am also a happy user of elm-ui and the idea of writing CSS again is not appealing.
The whole concept of pure functional programming with strict types and type inference feels like an higher form of programming and it resonated with me.
(This post was originally published in Medium)
Top comments (23)
Nice article! Here are my 2 cents, having used both Elm and Svelte in small/mid-sized pet projects.
Elm:
Svelte:
Hi Metin,
Thank you for your awesome comment. I basically agree with all that you said. Couple of small additions...
I think the official Elm documentation is also quite good with its own way of gradually introducing complex concepts.
About "I would love to see more UI libraries", I guess you are familiar with elm-ui but if you are not, have a look at it. If you mean something similar to Material Design, there are things like aforemny/material-components-web-elm but I personally prefe the elm-ui concept
Great post!
Thanks :-)
Have you tried purescript? It'a a lot less limiting than elm featuring stuff like typeclasses, higher kinded types and custom operators
I never tried it but I have been curious since watching the video youtube.com/watch?v=9kGoaUqcq4A. It seems that purescript is more for folks coming from Haskell. Coming from Javascript, Elm has probably a gentler learning curve
You sre right, purescript is a lot more complex, but I personally found elm limiting, so purescript was what I was looking for.
As for the downsides, there is a really good ui framework called halogen, but it doesnt have any docs for it's latest release, and a few react wrappers which have some problems with svg, so theres still work to do in this area.
Another thing ps has is the ability to run on the back-end by wrapping nodejs modules.
Hi! Just so you know, you can create apps in elm for use in the backend, using Platform.worker.
I never managed to get my head around Halogen, did any examples in particular help you out?
ReasonML is yet another alternative, also FP based, and a bit more "mainstream" I guess, because it's backed by FB (although it doesn't seem to have taken off as I expected it would).
What I dont like about reasonml is the syntax
This is really nice article. But the example you make is really far away from any comparission sense.
What I would like to see is how both solution scale when code grows, and how looks it from maintain perspective.
a + b example you make is even worst then hated Todo app examples.
Also both solutions have such huge principle difference, one static pure FP, and second emphasize JS even though it's compiler based.
Personally chosing just JS for compiled solution is very hard to accept for me. There is only one reason for such - allowing JS devs grasp Svelte fast, so it's marketing reasons. Svelte with no VDom and compilation to minimum bundles is very nice concept, I hope some better language could be used in such land.
I agree with you that these micro-example don't tell much about maintainability, but I think that they still tell many other things. They are so small that is very easy to highlight the differences, for examples.
Probably the largest application now available for a side to side comparison is realworld.io/ (I mention it in the post), but also from that code is hard to estimate maintainability.
Maintainability comparison is for sure an interesting topic that probably need to be approached from a broader point of view like, for example, statically typed languages (like Elm) vs. non-statically typed languages (like Javascript)
So what about the maintainability?
Svelte, being a 2-way-binding, without a single source of truth, and separating code from template, is just the same thing as Vue and Angular, just under a "nice syntax" umbrella. This is just screaming "absolutely unmaintainable" in any project larger than a ToDo app.
Just wondering , while “single source of truth” is not enforced by language/framework in Svelte, like it is in Elm via model, why couldn’t you, as a developer enforce, as a matter of habit and convention , via stores for example ?
Of course you can, the same way you can do functional programming with Java: By fighting against its intended use.
2-way binding is really the root cause here. 2-way binding means there is a double source of truth for every single variable, and for all of them as a whole: The DOM, and the app state. And assuming both will always be magically synced is naive.
It is really hard to reason about, for example, a text input that will be modified as you write, trimming text. Is the next key stroke event coming before or after you have modified the current text? What text should be printed first, the trimmed one or the untrimmed one with the new character? Will I lose the event? What if the trimming is being done server-side and should be debounced?
Really nice article even for me who knows neither of the technologies. Though I think there is a bug in Svelte example - till I change the both values it treats one of them as a string and does string concatenation
without quotations seem to work
Good catch. Actually the one with the double quotes is another example that is half way in the post. I thought that on save the Svelte repl would provide a new url instead is just overwriting the old one.
Now I fixed both examples, with and without quotes.
The problem with Elm is that you still have to write JS to communicate with Elm, since Elm does not cover all the needs.
The Elm Architecture is great, but Svelte is more flexible solution and its all about JS, no need to write bridges and workarounds.
My 5 cents
Great article !! ... thank you for helping on my decision, now it’s more clear for me -> I will use Elm for my personal projects and Svelte for my work ... in one year I will try to re-evaluate again (maybe my work will be migrated to use Elm or my personal projects to Svelte ... or keep them both, they are both great ... time will tell)
Dynamic type was always fun and keep myself relevant but so sorry elm is just <3
You can also check .NET world here:
websharper.com/
fsbolero.io/
It would be nice to have more like these tools compared