For the last year and a half I have been learning Haskell at a pretty slow pace, and along the way Ive been wondering…
0 How back end work is different from front end
1 Could a language and architecture similar to Elm and TEA ever be on the back end
2 If not why not.
Ive heard a lot of answers to these kinds of questions, and the theme seems to be that too many things can go wrong on the back end for a totally pure and controlled language like Elm to work on the back end. Its like the theory is that handling errors costs developer time and increases project complexity, so at some point if there are enough big fundamental things that can break, it makes sense to take big “leaps of faith” in your code, assume things work, and then only handle errors after the fact. In my Haskell experience, I can kind of see that: I notice when I write Haskell I spend a lot more time handling error cases than when I write Elm.
What do you think? Is this true? How could it be true? If it is true that “more things can go wrong on backend”, how?
Im not really convinced yet, but I suspect the people Ive talked to know better than me. My best guess for why that would be is that there are more numerous and complicated side effects on the back end.
Do you know about https://wende.github.io/elchemy/? I have not used it but if you are comfortable learning Elixir in addition, this might be a way for you to explore how an elm like language works on the backend.
As far as I have understood the reason elm is “not for the backend at this time” is more a question of focus in terms of resources and language design.
As for your speculation on requirements for some “worst case error handling”, I personally know of no language that has been designed for the backend that would not have it. Go for example strongly discourages use of “panic” (which works like “try/catch”) because readability is a main concern and plain old return is easier to follow. They still have it though, presumably because crashing on the frontend affects maximum one user,l while a crash on the backend might affect all users.
When thinking about this, I think you should separate Elm and TEA. Elm is the pure functional core language, and TEA is the nice pattern we use for writing UIs in an event-driven style.
You could have back-end logic written in a TEA style - every interaction outside of the core runtime for each thread would be a Cmd. If you code up some ports for database or file system interaction, and use a Platform.program you can have a go at writing a server running on Node.js.
If I was writing a server in say Java, I might like to have a cache that is shared accross all threads. Perhaps one call does some expensive work to query databases and render the result as an HTML page, then places that in the cache. A call in a different thread simply picks up the result from the cache. Elm the core language doesn’t give us any primitives for working with concurrency, so this cache would not be possible to write in pure Elm. If the cache was available through a port and Cmds it would be usable from Elm though. Just treat each Elm thread as an actor in the actor concurrency model and assume a kernel would be provided that gives us tools like that to make it usable.
To me, the top thing for an Elm-like language on the back-end is to decide its concurrency model. Is it the actor model (I assume Elchemy takes this approach)? Or would the language provide a more primitive set of concurrency operations and let programmers choose their own model on top of that? Elm is very close to the language Standard ML, which has been extended to Concurrent ML and that is where I would look for some inspiration.
An important difference is maintaining and migrating state. When using Elm on a backend, I keep the application state for a longer amount of time. To make the state robust, it is recovered after an unexpected shutdown of the host. Until recently, I did not even know of any framework which takes care of this basic functionality.
Since the application state is kept for years, I also need to migrate it when I switch to a new state model. So I have a function like this:
migrate : ModuleContainingPreviousVersion.State-> State
Migrations are expensive so far because the tooling to automate this is not yet here.
Yes. But there are also engineering challenges remaining, for example using indexes to speed up operations on collections.
I have seen Elchemy, and its really cool. But so far it doesnt look like theres an example or even psudo code of an Elchemy webserver. So while it has the syntax and purity of Elm, theres still a question in my mind about what it would be like to make a back end application in Elm, and whether that would be a bad experience.
I’d be curious too! I cant recall any explanation.
I have. Thank you for posting it tho, its been a while. I dont think those points represent where Im coming from. Im not trying to avoid JSON decoders, or server side render, and I dont presume that since Elm is good one domain its great in all domains.
Maybe I could clarify my curiousity with this hypothetical:
One could make the Elm compiler compile to Haskell, in a fairly direct way. The Elm Maybe goes to the Haskell Maybe and so on. You could also make an Elm package much like Browser.Navigation that sets up a server and has a Msg for different routes that could be hit. From there you could make a back end application in Elm. But I notice, that this hypothetical back end Elm server (as I imagine it), wouldnt really look like the Haskell back end applications I have seen. It doesnt even seem to resemble the style and philosophy of Haskell projects. It certainly seems like it would be wildly different than Erlang applications, that dont have any type safety and sort of expect to fail somewhere.
So why is that? Is it because there is something about the back end domain, which even the style and philosophy of Elm are poorly suited for?
Thank you. This is perfect. Really clarifies what would be needed from an Elm backend.
Great. But when you say “state”, do you mean like the state a computer would keep in memory, or like in a database on a disk? To me this is one candidate for maybe why Elm wouldnt work on the back end. On the front end you can just have this one simple thing called Model and you are good. But for back end applications, (I think) theres very little in-memory state, and a lot of a lot of the important stuff is in a database. So at the very least the “state” part is more complicated, but also in some cases state is seeminlgy non-existent because you could have a perfectly good rest api that has no state at all (it just grabs stuff from a database and sends it off).
The state I meant is the state which is given to the update function in the Elm app. I was describing the perspective of the App Developer, who writes his Elm app which a top level update function. In the projects I have seen so far, it was OK for clients in the front end to reset their state, because in this case the client can get the (relevant portion of) the last state from the backend. In the end, it was the job of the backend to keep the records. That is why I mentioned this as what is different between back end work and front end.
Since you mention in-memory storage and disk storage, a quick look on the implementation side: Of course, at the time I start the backend, I need to pick a store for persisting the app state. For now, I am using a plain classic file system API to do this. Having the Implementation reach down to the file system is what prevents the loss of the app state when an unexpected shutdown happens. This implementation is part of the hosting framework, the Elm app does not know about this.
Why is the state part more complicated? Is that database outside of the Elm Model? If so, why? Did you encounter problems when keeping the database in the Elm Model?
Yes! This is the question that needs to be answered first.
To move to the backend “The Elm Way”, a lot of research and design would have to happen. There is also a need for domain knowledge. I dived a little bit at one point and realized that things are way more complex than I first thought.
Producing a proof of concept is trivial, producing something that would make sense to adopt instead of some other established technology is an entirely different thing.
I was telling a Java programmer about Elm recently. A Java programmer who works on low-level Java code for HPC, He didn’t like that Elm was immutable giving the example of quick sort. Quick sort is only quick sort if you are actually using mutable arrays - you can write the algorithm on top of the immutable HAMT arrays we have in Elm, but its not really the same algorithm. I did explain to him that the performance of Elm is very good, good enough for its domain generally.
But its a fair point. Web servers written in Java can be extremely efficient with low-latency and high throughput and handling thousands of connections - in part because of mutable arrays. This feels like something that Elm on the back-end could never compete with.
I imagine that a back end Elm would focus more on higher level things, like writing business logic or web APIs. When I think of all the service logic I’ve written or seen in Java, I now think of how much better that would be in Elm.
I think the simplest and least general role that Elm can play on the back-end right now, is server side rendering with rehydration on the client. There is no official support so its a bit hacky, but its been shown it can be done.
Here is a concurrent connections benchmark showing Elixir and Clojure (both based on immutable data structures) outperforming every garbage-collected mutable-arrays language in the benchmark. (Only C++ outperformed the immutable-collections languages, which is presumably less about data structure choice and more that C++ never has to pause for garbage collection.)
Assuming mutability of sequential collections were somehow a significant bottleneck in a server’s ability to do low-latency high throughput processing of many concurrent connections, the focus of any concern should be whether Java’s slow and clunky mutable arrays can hope to compete with the obvious superiority of Elixir and Clojure’s immutable collections.
Just to be super clear about it: the real point here is that mutable arrays don’t make a difference in this domain. It’s all about how the servers and VMs are architected.
Erlang / Elixir is heavily used for low-latency and scalable web services (like whatsapp). Erlang doesn’t support mutability at the VM level.
A lot of backends today does not need a computational fast language, but a language which is good at concurrency. Functional languages tend to be great at concurrency. So there’s no reason Elm couldn’t contend in this space someday in the future.
The latencies on this concurrent connections benchmark are of the order of tens of milliseconds. Note the scale on my graph is in micro-seconds, and this was measured using a network tap so its the real network message in/out latency to the wire. This is 3 orders of magnitude faster than your typical web server, 100% in Java and relies heavily on mutability to avoid creating garbage. Of course, we are comparing different tests here, but I put it up just to show what some people are striving for on the server.
That said, your benchmark does show that in many cases immutability isn’t a bottleneck, I am pleasantly surprised by it. Certainly good enough for the web, just perhaps not for electronic trading or HPC.
Extreme efficiency can be important in HPC, because a factor of 10 speed up can mean you have 10x less servers to pay for. In many cases all the difference between a profitable operation and an unprofitable one.
It looks like the SAFE stack folks (F#) are looking into the possibility of ‘server-side TEA’ with Elmish.Bridge. I haven’t used it myself so I’m not sure how well it works or how far along it is in development.
Isn’t Elixir basically what Elm would be on the backend? No runtime errors, great developer experience, carefully designed to only include necessary features et.c. They seem super similar in philosophy.
Elixir is a dynamic language. This makes it very, very different from elm. It is also a functional language so that makes it somewhat similar BUT, it has an imperative style that feels again, very, very different from Elm.
A crash is a runtime error, so you are technically right. In practice, Elm has a track record of never having runtime errors; Erlang has a track record of nine nines availability. What I try to say is that they have a similar approach to language design:
Ensure that the program does what it’s supposed to. Erlang by crashing and Elm through types.
They are both functional
Focus on readability and developer experience
One right way to do things
Simple and minimal amount of features, chosen for the domain.
Since backend and frontend are different domains the languages naturally end up different. They choose the architecture and features to include so that they best fit their respective domain. This to me makes Elixir and Elm more similar than Elm and Haskell. Not syntactically and in terms of specific features but in their approach to language design and belief of what makes a good programming language.
Elm has tight control over side effects, Elixir does not.
Elm tries very hard to avoid runtime crashes, Elixir does the exact opposite.
Elm has no way to call JS, it all has to go through ports, Elixir allows you to call Erlang directly.
Elixir also has Lisp-style macros, and Clojure style protocols. It’s really closer to Lisp than anything else.
Don’t get me wrong, I’m a huge fan of Elm and Elixir. But they’re radically different in developer experience and design.