I find it far too easy to initially focus on one small part of the problem while losing perspective on the bigger picture. For instance, I will begin by rendering a simple scene, followed by splitting out components, and finally adding in signals. While this is generally a good approach, in my mind, it is sometimes difficult to retrofit patterns onto the basis of the application I have already written.
There are some really excellent resources covering the architecture of applications in the Elm language. The canonical article on the subject can be found on the official Elm site and covers everything at a high level. The best example is likely TodoMVC ( source ) ported to Elm which uses many the concepts from the article to create a rich and simple interface, showcasing just how powerful Elm is. Another great and extremely polished example is Dreamwriter ( source ), which also incorporates the separation of components as outlined in the original article.
The only problem with these examples, and the theory behind them, is that they are too fleshed out, too complete. It is difficult to extract the core concepts from the specific details of the problem they are solving. In attempting to retrofit this pattern onto my own programs, I had to remove everything extraneous and reduce the implementation to the fundamentals.
First, I will present the source and result of a very, very basic (practically trivial) user interface in Elm. It consists of nothing more that a button that increments a value, but this simple scene gives us the foundation upon which we can build larger applications.
This example consists of the four core components for any such application:
We will discuss each in turn.
Most applications are not very interesting without user input. We choose to represent all user input as a union type This gives us the ability to easily pattern match on the user action in a case statement, which will be useful very soon. In this particular example, the Action
type can only consist of either a NoOp
or Increment
.
We then create a signal channel of actions. The actions
method creates a new channel that will default to the NoOp
value. The incrementButton
function creates a new button that will send the Increment
value to the channel.
The state
function begins to really pull all the pieces together. Here, we use Signal.foldp
to create a past-dependent Signal State
. The State
type alias is defined as having only one field, a value
of type Int
. We start with an initialState
where the value
field is set to zero. The signal we provide to the foldp
function is the result of passing the actions
channel to the Signal.subscribe
function.
We then define a step
function, of type Action -> State -> State
. Because of the pattern matching on union types, the case statement is very simple to follow: if the action parameter is a NoOp
, simply return the original state, but if it is an Increment
action, return a new State
record with its value
increased by one.
The final piece of glue is very typical in Elm, the main
function of type Signal Element
. In this very simple example, we are using Signal.map
to call scene
with the current state.
While this example does not actually do particularly much, that is precisely the point. You can transparently observe how the pieces fit together to make up the core of the application. In practice, there are many more steps to be made, but the general principles remain the same:
Generally speaking, to add real functionality, we only need to add more values to the Action
union type, extend the case statement in step
, and create more sources of input like incrementButton
. In reality, however, our applications will be much more maintainable if we create separate components and nested Action
and State
types (which the architecture article covers in detail).
In conclusion, the basic pattern that is being used in more complex and feature rich Elm applications can be distilled to very little, but is an extremely powerful concept that can be extended to a great degree. This is the case to such a degree that I will likely start with a core similar to this, rather than some trivial scene, when writing new applications.
23 Dec 2014