Elmification of Swift

Implementing the Elm architecture in Swift

We recently took a venture into the fantastic world of Evan Czaplicki’s Elm, as part of an exercise in deciding upon a framework or architecture to build our new editor at Momentumworks. Although we ultimately decided to go with a Native application approach (written in Swift), we found the Elm architecture particularly pleasant to work with, and decided to spend a little time investigating what it would take to replicate that architecture in Swift. (Note that this article is written with the caveat that we are Swift novices, and openly welcome any comments or constructive criticism on our code)


So first off, why should I even care about Elm or its architecture?

Because I told you so. Just kidding. Kinda.

But seriously, you should care because Elm allows you to write your UI in a declarative manner, using fractal and referentially transparent components, making the app significantly easier to reason about, and easier to test (given an arbitrary model, you can calculate the result of rendering a given component of the UI, or even the entire UI)

Go on…

To give a brief background on Elm, it uses Functional Reactive Programming, and provides an almost silky smooth Haskell-like syntax for building webapps. FRP means that you are essentially reacting to streams of some given events (or values), using Functional Programming techniques (in Elm these streams are called Signals). By using FRP we are able to write our UI in a completely declarative way (more about that later). At this point, if you know nothing about FRP, go and have a quick read of André Staltz’s fantastic intro to FRP here, and then carry on.

The Elm architecture results in fractal applications, that are built from components that all expose a Model, an Update function, and a View, as follows:

type alias Model = { … }

type Action = DoThing | DoOtherThing
update : Action -> Model -> Model
update action model = …

view : Signal.Address Action -> Model -> Html
view address model = …

So in here:

  • The Model is simply the type of the underlying model of that component (so if it were a basic text field, the Model would be a type alias for String)
  • The Update function takes an action and a model, applies the action to that model, and returns the result (note that this is side-effect free as it is completely functional)
  • The View takes a given model, and returns the corresponding HTML for that model (as we’re building a webapp). That Signal.Address Action thingy is the event stream for sending new actions down (so, if for example your page contained a button that sent a “DoOtherThing” action, you would do so by sending that action down this stream)

Frac… what?

Fractal. As in, if you keep breaking down the components into the components that they are made from, it all looks the same. By sticking to this basic Model, Update, View pattern throughout, we can define some very simple base level components, and then build up much more complex ones from them, resulting in an application that looks the same, and is easy to understand and reason about, at every layer:

As the View function is a pure function that effectively maps from a given Model to HTML, it frees us up to write this in a completely declarative style, so for example the view function for an individual counter would be:

view address model =
div []
[ button [ onClick address Decrement ] [ text "-" ]
, div [ countStyle ] [ text (toString model) ]
, button [ onClick address Increment ] [ text "+" ]
]

Don’t worry too much if the syntax is unfamiliar to you, the point is that we can take a model and build the HTML for that component in a manner that is referentially transparent.

Excellent. But how can we achieve this in Swift?

What a fantastic question you have there young squire! To answer that, first off let’s introduce you the wonderful RxSwift, an implementation of ReactiveX for Swift. Remember how FRP is all about using streams of events or values, and Elm’s instance of these are called Signals? Well, we can use RxSwift to provide these streams, by making use of its Observables, and again applying Functional Programming concepts to them, e.g.:

Here we have an Observable<Int>, and are applying a map() to it. What the hell does that mean? The top part represents the value of the Observable at any given time t — the axis in this case represents time, and the circles represent the value of that Observable being updated. The bottom part then represents the new stream (or the new Observable) that we have created as a result of applying that mapping function.

If you find this a bit confusing, there is actually a great site called RxMarbles that provides interactive diagrams allowing you to see the results of applying the various operators to Rx Observables, so you should absolutely check that out — here is the interactive diagram for the above map().

Cool story bro, just get on with it…

So first off we’ll create a basic Component:

protocol Component {
typealias M // Model
typealias A // Action
typealias C // Context
    static func initModel() -> M
static func update(action: A, model: M) -> M
static func view(model: M, context: C, base: Base) -> Node
}

This right here forms the basis of our fractal pattern. We have the same Model and Update as in Elm. You may have noticed that the View here takes a context, rather than a stream —the Elm architecture also advocates the use of a context. The “Node” returned by the view func is going to form a virtual view hierarchy, which we’ll show shortly. And as for that Base, it’s just a struct containing the component’s ID, its parent’s ID, and its frame.

As we aren’t returning HTML, but are rather returning some form of an NSView, we need to have a base component to build up from:

protocol NativeComponent : Component {
static func create(frame: NSRect, model: M?) -> NSView
static func wireUp(context: C, nsView: NSView) ->
PublishSubject<Any>?
}
class ViewComponent : NativeComponent { … }      // Parent NSView
class ButtonComponent : NativeComponent { … } // NSButton
class TextFieldComponent : NativeComponent { … } // NSTextField

So now we have introduced a function for building an NSView from a NativeComponent, and a function for wiring up a component which returns a stream (PublishSubject<Any>) for poking any new model updates down (we’ll come back to this shortly).

OK, now taking a step back, we returned a Node from the Component’s view function. This is basically going to be our virtual view hierarchy:

enum Node {
case View([ Node ], base: Cruft)
case Button(ButtonModel, ButtonContext, base: Cruft)
case TextField(TextFieldModel, TextFieldContext, base: Cruft)
}

All we care about in our very limited virtual hierarchy are parent views, buttons, and text fields. Simple. We can then use this to build up a virtual view, which we will then use to render our app.

Blah blah blah, can you at least make this a bit more concrete?!

So let’s build a simple app from this, that creates two text fields that are linked together. First off, we’ll define the model:

class LinkedLabels: Component {
typealias M = TextFieldComponent.M // Model
class func initModel() -> M {
return TextFieldComponent.initModel()
}

The model in this case is mega simple: it’s just the model of the TextFieldComponent (our most basic of building blocks), and so as such our initModel() can just delegate onto that component’s.

enum A { case Update(TextFieldComponent.A) } // Action
class func update(action: A, model: M) -> M {
switch action {
case .Update(let tfAction):
return TextFieldComponent.update(tfAction, model)
}
}

The action is only slightly more complicated, but that’s because it wraps the TextFieldComponent’s action. But in doing so we can also now just delegate calls to this component’s update function in a very similar way as we did with the model.

struct C { // Context
let dispatch: A -> ()
}
class func view(model: M, context: C) -> Node {
// Node.TextField(…)
let left = TextFieldComponent.view(
model,
context: TextFieldComponent.C(
dispatch:{context.dispatch(A.Update($0))}),
base: /* ID, Frame etc. */
)
  // Node.TextField(…)
let right = TextFieldComponent.view(
model,
context: TextFieldComponent.C(
dispatch:{context.dispatch(A.Update($0))}),
base: /* ID, Frame etc. */
)

// Node.view([Node.TextField(…), Node.TextField(…)])
return ViewComponent.view([left, right],
context: NSNull(),
base: /* ID, Frame etc. */
)
}

(Note that the context for TextFieldComponent is exactly the same as the context for this Component)

Our context contains a dispatch function, which wraps one of our actions, and when creating the TextFieldComponent’s context we further wrap that.

Right… But that’s only a virtual representation of the view…?

Correct, so now we need to turn that into an actual NSView. This is where the create() and wireUp() functions from the NativeComponent come into play. We’ll just have a look at the TextFieldComponent’s:

class func create(frame: NSRect, model: Model?) -> NSView {
let nsTextField = NSTextField(frame: frame)
nsTextField.stringValue = model ?? ""
return nsTextField
}

Well that was simple enough! How about the wireUp function?

class func wireUp(context: Context, nsView: NSView) ->
PublishSubject<Any>? {
  let channel = PublishSubject<Any>() // (1)
let nsTextField = nsView as! NSTextField
  channel.map{“\($0)”}  // (2)
.subscribe(nsTextField.rx_text)
.addDisposableTo(disposeBag)
  nsTextField.rx_text   // (3)
.subscribeNext({ text in
context.dispatch(A.Update(text))
})
.addDisposableTo(disposeBag)
  return channel
}

There’s a lot going on here, but don’t worry, we’ll break it down:

  1. First we create a stream for updates to the textfield down. It’s a PublishSubject because we need to be able to push updates down this stream (it might have been better to create this channel elsewhere and pass it in to the function — the idea was to create a PublishSubject<TextFieldComponent.Model>, but as Swift doesn’t support variance we couldn’t support this at the protocol level)
  2. Next we subscribe to that channel, map its value to a String, and then feed that into the NSTextField’s .stringValue (via the .rx_text property that has been added to the protocol by RxSwift). This means that any time a new value is passed down that stream, the NSTextField will be updated with that value.
  3. When an update is made to the NSTextField’s .stringValue, we want to dispatch an .Update() action

The result of this is that now we have an NSTextField that we can push model changes to, and that will feed us with delicious Actions when someone starts typing into it.

OK, I think I get what’s happening now, but how does it fit together?

So now that we can build these components, and can wire them up, we need to write a main loop that will respond to actions and will update the UI accordingly. First off we will concentrate on generating the new model after each action:

let actions = PublishSubject<AppAction>()

let loop = actions.scan(App.initModel()) { (model, appAction) in
let
updatedModel: App.Model
switch appAction {
case .Init:
updatedModel = model
case .Action(let action):
updatedModel = App.update(action, model)
}
return updatedModel
}

Se here we are performing a scan of the action stream, which is similar to a fold, but it emits the result of each application of the closure it is applying (see docs here). This will then apply each action and then return the resultant model (we have a basic init action which just returns our initial model, and a wrapper action that will delegate to the App’s update function).

Next we want to subscribe to the models produced by that loop, and use them to feed our UI, as follows:

loop.subscribeNext{ model in

1 Generate the virtual nodes:

  let updatedRoot = App.view(
App.Context(dispatch: { actions.onNext(AppAction.Action($0)) }),
model: model,
base: base)

2 Diff these against the previous virtual nodes:

  let allNodes = updatedRoot.flatten()
let allNodeIds = Set(allNodes.map { $0.base().id })

let (addedNodes, updatedNodes, removedNodes) =
/* compare allNodes to existing Nodes */

3 Create the NSViews for the added nodes:

  for vnode in addedNodes {
let component: NativeComponent = componentForNode(vnode)
let nsView = component.create(vnode.frame(), vnode.model())
// Add nsView to our collection of current views
// Add nsView as a subview to either its parent or the root view

let stream = component.wireUp(vnode.context(), nsView)
// Add this stream to our collection of all streams

stream?.on(.Next(vnode.model()))
}

(We cheated a little here by including function on the Node enum to illustrate a point — the full code is linked to at the end of the article)

4 Remove any nodes from the view that are due to be ditched:

  for vnode in removedNodes {
let nsView = // grab and remove from collection of current views
nsView.removeFromSuperview()
let stream = // grab and remove from collection of all streams
stream?.on(.Completed)
stream?.dispose()
}

5 Push the updated model to any updated nodes:

  for vnode in updatedNodes {
let nsView = // grab from collection of current views
nsView.frame = vnode.frame()
let stream = // grab from collection of all streams
stream.on(.Next(vnode.model()!))
}

6 Perform cleanup, and push the .Init action down the actions stream:

}.addDisposableTo(disposeBag)

actions.on(.Next(AppAction.Init))

And there you go! We now have a loop that updates the model in response to actions, generates a virtual view, diffs that against the existing view, and updates the UI accordingly.

Nice! So what’s left?

Well we haven’t covered styling, which is a pretty important part of this all. Elm achieves styling via CSS (as you’d expect) — we’d like to be able to apply styling in some similar manner, but that’s still yet to be done. Alternatively, it could become part of a component’s model. There is still much discussion needed around this.

Also, RxSwift mainly focusses on iOS components, whereas we need to use OSX ones, so there are a lot of components that are currently unsupported (though we have started working on adding those in to RxSwift)

Lovely. So what were the overall takeaways from this?

By using an architecture like this, we are able to:

  • Create applications that are fractal in nature (resulting in a pattern that pretty much writes itself)
  • Write the UI in a completely declarative style
  • Define components that are easy to reason about, improving our ability to test them greatly

Anything else?

The code for this exploration can be found on Github here: https://github.com/momentumworks/swift-elm

We’ll be continuing this investigation somewhat, but we have taken a bit of a pivot and are looking into how the components and their models can be stored as metadata. We will be documenting this on the way, so look out for that.

If you have further questions, or would like to get in contact, I’m on the Swifters Slack channel, and Twitter.

Oh, and of course, if this is of interest to you, we’re recruiting!

Resources

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.