Historical origins of representing side effects with commands?

I’m not an active Elm programmer, my day-to-day language is C++, and I have some experience with Haskell. However, I’ve gotten curious in how Elm handles the issue of side effects in programs.

In trying to find ways to separate effectful code from pure computational code, I’ve sort of “re-discovered” the already existing pattern of replacing calls to side effect causing functions with returning data that represent those side effects, which are then carried out by the “outer” part of the program, which in Elm I believe is called Model-View-Update.

To me it’s a very elegant pattern, but I can’t seem to find it discussed online outside of Elm circles.

Is there some documentation of how MVU got developed? Are there any specific program language theoretical underpinnings for it, or was it sort of naturally grown out of the usage of Elm?

4 Likes

I’m on mobile and don’t have time for a deep dive, but briefly I think MVU was something that evolved over time. Elm in the earlier days (before I even used it much) was more about functional reactive programming. Back then the main abstraction your app used was Signals. Elm 17 changed that and you might find news/farewell-to-frp interesting.

There is also the IO type in Haskell, which predates Elm. I have never done enough Haskell to know how it works or differs from Elm, but Haskell is a functional language with managed side effects and the Elm compiler is even written in Haskell, so it must be considered an influence? Perhaps someone else can explain the difference between IO and Cmd and MUV for us all?

I would say that, from what I can tell there’s one big difference in that with IO you will make calls directly to things like reading from a file or printing to stdout. Even though the implementation of IO “buffers” the IO calls and only actually executes things lazily, in the program text you still have calls like printStrLn directly in the place where you want the effect to happen.

In Elm it seems like we’d instead dispatch a Print command (ignoring the fact that there’s a Debug.Log etc), and it’s understood that this really is just a token, it has to be received by the Update before it’s actually ran.

Comparatively, in the Haskell code (ignoring laziness) the source code appears to directly call printStrLn. Actually implementing an IO monad outside of Haskell is really tricky, but creating your own Cmd in something like C++ has turned out very easy for me.

Because I’m interested in using Cmd outside of Elm, I’m curious about its theoretical foundations, hence why I’m interested in the history of its development.

To understand the history, you can start by looking at Evan’s thesis. The Advanced Topics page has links to a lot of useful videos and to the thesis.

In short, the foldp (fold from the past) function took an initial state and a Signal of events and produced a Signal of the state. It allowed for the state to track the events. The signal of state would then be mapped to a signal of Element which was the UI back then. So, even back then you would have a State (that later got rebranded as Model) as the second argument of foldp, a signal of events (third argument) and a function that would take an event and the state and would produce the next state.

At one point elm-html was created and people used different approaches to managing the state and putting html on the screen and this prompted some design work on Evan’s part that resulted in a tutorial called elm-architecture-tutorial. You can already view the now familiar Model, View and update nomenclature.

Later the tutorial was refactored using a helper library called start-app that took care of all the wiring and made the architecture even more explicit. People started using start-app and soon it became the recommended way to structure the elm UIs.

From what I remember, the use of elm-html prompted some refactoring of elm-http and the release of elm 0.15. This resulted in StartApp 2.0 which introduced the now classic Elm Architecture with side-effects. The elm-architecture-tutorial was also updated to include examples of how to use elm-http.

Later, the various helper libraries like start-app or elm-effects were refactored and streamlined into elm-html becoming implementation details. Eventually, the code got extracted into elm/browser.

The elm-architecture-tutorial evolved and became The Elm Guide.

One more detail. Commands are just a thin layer around Task. If you want to understand side-effects in Elm, you need to understand the implementation of Task.

6 Likes

Beware, it’s hard to come back from managing errors this way! Eh! You can now even use managed side-effects in TS (https://effect.website).

You can find this concept related to other terms like Monadic I/O, or in actor systems (=message passing architecture for distributed or heavily multi-threaded computing, like Akka in Scala, the entiere Erlang ecosystem or actors-rs in Rust). I bet there are C++ codebases out there featuring some of this concepts, it’s gotten more popular lately.

cheers

2 Likes

If you’re interested there’s a simplified version of TEA as a function state -> effects with what comes back where effects aren’t triggered by events but by state changes just like the ui: elm-state-interface.

In college, when I was getting into functional programming, I stumbled upon the paper “How to Declare an Imperative” by Philip Wadler in ACM Computing Surveys, Sep. ’97. This may be the origin of Haskell’s Monad concept, but I can’t access the full text anymore to check. In it, he introduces the “bind” operator, which in Elm syntax would look like

(>>=) : IO a -> (a -> IO b) -> IO b

where IO a represents a side effect that produces a value of type a (if I recall correctly). Haskell still has this operator, but they also have syntactic sugar – do blocks – to make it look like imperative programming (see https://wiki.haskell.org/Monad).