we did it our way No images? Click here Bucking or setting trendsPhoenix LiveView was the instigating feature that many frameworks followed. They shipped their own WebSocket- or AJAX-driven solution to mimic the approach. Chris McCord, credited as the creator of both Phoenix and LiveView, has covered the ways he tried to achieve all this in Ruby. The new implementations might be better than what he achieved. It may be more feasible now. They are still fundamentally retreading that ground with Hotwire and Livewire, etc. I don't begrudge them that but I'm also not particularly enthused by those solutions. The question "would you switch to Ruby now that it can do the same?" doesn't compute. It is not the same and it can't be the same. Key to why people that look at the details of Phoenix LiveView come away with enthusiasm is in my view not just one useful thing or one neat abstraction. It is a bunch of useful designs built on top of each other in a thoughtful way to achieve a specific high-level design. This design abstracts away a lot of annoying parts of building interactive web apps. There are a few fundamental challenges in computer science. Managing state is one of them. This is why cache invalidation is hard. And it is why frontend frameworks keep inventing state management systems. LiveView is a design to constrain the complexity of dealing with state for the specific purpose of a web application. The design can be copied in any language but the foundation it was built on cannot. Many have tried to port Erlang to their thing. It is not impossible of course, just very difficult to do well. Copying LiveView means copying the surface-level experience. But why did Chris decide this was a good idea to do in Elixir at all? The preceding step was Phoenix Channels. An abstraction on top of WebSockets where each connected user gets their own process, keeping their own state. That's not hard right? In any language. Just keep a datastructure in a map of connection IDs of some sort and bam. What was that about a process? Well, this is isolated state that can be operated on concurrently. Actors. LiveView is a web-UI abstraction on top of Channels. Channels is an abstraction for WebSocket communication, on top of GenServers/Actors. The GenServer is an abstraction for state and resilience on top of the Erlang process and message passing. The Erlang process is a primitive for simple concurrency and consistently low latency soft-realtime applications. Message passing is a mechanism for safe communication in highly concurrent systems. Both are primitives in the abstraction of the BEAM virtual machine and Erlang. If we go any deeper we hit the actual implementation and a lot of C code. From the fundamental primitives it is built on we get concurrency and low latency/realtime characteristics along with the safe communication mechanism of message passing. The foundational abstraction brings in resilience in the form of supervision trees and a design for managing state and standard practices for message passing. Phoenix builds on HTTP servers, either Cowboy or Bandit. Those leverage all of these primitives (and others) to perform well as web application servers on a network level. Then we are back to Channels which provides abstractions out towards web browsers, retaining all these capabilities of the underlying foundation and making the communication straightforward for the implementer, us developers. LiveView takes that a step further. It obviates the need for you to specify a protocol, contracts, serialization, all that. It heavily optimizes your templates to both minimize bytes over the wire and make generating change information easy for diffing. It also simplifies everything by constraining the state to the server. The fundamentals of Ruby, Python, Node.js and PHP are very different from Erlang and Elixir. The languages live on a similar abstraction level, high-level and dynamic. The runtimes are written in C or similar. The goal of Node.js was leveraging V8, so it is pretty fast for specific workloads. Ruby, Python and PHP were never built with concurrency or performance as a central consideration. They have since tried to bolt that on. They were also all early with nice easy-to-use languages and succeeded wildly in adoption where Erlang struggled. Elixir has been a fresh run at adoption on those healthy fundamentals that Erlang has. Much like you can see Rust being a fresh run at a systems programming language and being much appreciated for it. LiveView is the canonical example of leveraging the BEAM by stacking good design decisions on top of it and really leaning on its capabilities. It is not the only one. Membrane for example does media pipelines with Elixir and the way it works means that your media pipeline can tell you about what they are doing as they are doing it. Elixir is fundamentally very reasonable for low-latency use so this makes it possible to do a live-streaming pipeline where it does some of the work. This is a risky proposition in many high-level dynamic languages as they don't generally do concurrency and low latency well. And you get some upside on resilience compared to faster languages such as C, C++ and Rust. Nerves is another example. You can build IoT and other connected devices by writing primarily Elixir and having Elixir operate the device. Essentially the BEAM replaces your systemd or init scripts and becomes the major interface with your underlying Linux OS. The model for handling failures and the way the VM is built to manage a multitude of simultaneous activities makes it a very compelling way of building. It would be a really bad idea in just about any other runtime. Nx is an interesting and more recent one that seems promising and is definitely useful. Being able to bring ML inference (and training, though I haven't tried it) into your Elixir application really makes it trivial to use a model. To my knowledge there is no other ecosystem that integrates AI models so nicely into the backend tech. I imagine you have a really short path to doing it in Python but I also know that you really want to be careful about how much work you do in your Python backend. GIL and all. Erlang has for the longest time had a batch of tools that no other ecosystem I know of can even aspire to. Let's take recon, you can log into your system and tell it to record some function calls, you can measure their performance, dig deeper and at runtime, in a production system, figure out why you have a performance problem. A process leaking memory? You can go in and find that sucker. Even restart it. The amount of things you can do with the BEAM at runtime is mind-boggling. See also Live Dashboard that ships by default with Phoenix and more recently Orion, a dynamic distributed profiler. If you want to copy this to Python, Ruby or PHP you would need to instrument your code or do the wildest monkey-patching the world has ever seen. PHP runs in such a particular way that it'd be even worse. And fundamentally these languages aren't built for this. Erlang was and is, and so Elixir is. Everyone wants observability, everybody wants error tracking. This ecosystem could take that further than any other. Whenever we diverge from the mainstream we aren't quite compatible and it risks being niche or seeming weird. It is also where the magic can really happen. Most runtimes don't comfortably run absolute tons of WebSockets. Ours does. Most runtimes doesn't manage a lot of in-memory state, performing concurrent operations on it with minimal latency. Ours does. Most runtimes can't answer questions about what the internal state is as it is running. Ours does. I think we should keep leaning into that. Explore our possibilities, push our boundaries. Don't constrain our exploration to the limits of other ecosystems. What are your favorite unique traits about tech that you enjoy? Feel free to email or federate at me via lars@underjord.io or as @lawik@fosstodon.org. Thanks for your patience. Sometimes I write a long one. |