Declarative Swift in Action

Fully Declarative Swift for Real World Projects

In this article I want to explain how we can write fully declarative Swift. This coding style emerged somehow naturally when I started working on an architecture that I had designed specifically to be easily adaptable in agile teams — actually the goal was to unlock agility for teams in the first place.

In this article I will focus on the actual syntax and show how it is aligned to the semantics of the app.

I will not show every facet of my architecture (project name: Core|UI), but you will find a link to a series of articles I wrote about it at the end of this article. One thing I want to mention is, that it is fractal, meaning it uses (in theory: infinite) hierarchies in which each level uses the same patterns as the others. And this article will contain some of those patterns.

But first I would like to clarify some terms I will be using.

Object Orientation: I want to define this term as I do believe that we do not generally share a common understanding of what OO is. For me and in context of this article OO is defined by is usage of messages being send between objects. Objects can receive and send messages at any time (n to m), which sets it apart from FP, where we have a strict 1 to 1 relation for a function being called and it returning. As I will explain more in depth later I do not limit my objects to be of a certain type, like class and struct.

Functional Programming: FP has become a more popular contestant for OO over the past years. It is called as such as it actually uses the mathematical idea of a function (pure; state/context-less) as its main building block. You will see that I use a combination of functions (partially applied functions) to create our modules interfaces, yet I want to emphasise that this isn’t FP, as I actually use this functions to create and capsulate module state and functionality and also to communicate in an OO fashion. Though I have taken many ideas from FP, such as immutable data types and immutable state.

Immutability: Without going too much into detail we always should strive to create code that is as immutable as possible. Why? Well, I would refer you to the search engine of your choosing as their are many important discussions ongoing. But to keep it short: types and data that cannot change also can’t change unintentionally — a whole category of crashes and bugs eliminated.

Module: In this context a module is a scope defining element with maximum one input and maximum one output. Modules understand either an app-wide message type and use it for their input and output, or they offer their own types, like Request/Response. Which is to use when I will explain during this article. In any case: I call those types the Module’s DSL (Domain specific Language).
Usually the goto type would be a class, I will try to explain why I consider other types better suitable for the job of a module than those.

Now let’s look at some canonical example

sumImperative is created in an imperative style, by explaining the task at hand step by step. sumDeclarative0 is created declarative by creating an array of values from 1 to 100, which is then reduced. To reduce it, we provide a starting value (0) and a simple addition function (result, newValue in result + newValue), where result is either the starting value or the result from previous calls of the provided function. As the provided function is only adding two ints, we can pass in the +-operator as function instead, as seen for sumDeclarative1.

I argue that this style is more natural, as we as humans do not tell each other the steps to do certain things over and over again.

If I needed someone to sum up all numbers in a list, I would not ask them: “Please, type the first row’s number into the calculator, press ‘+’, now repeat with the next row and continue till you run out of them. Then give me that number”. No. I’d say: “Please, sum up all numbers in that list and let me know the answer.” And once I got used to the FP’s nomenclature, I find

expresses that better than

  • Imperative Programming: Telling the computer how to do something.
  • Declarative Programming: Telling the computer what to do, while using math lingo.

This is the point where most articles on that topic ends. They usually show you how to use declarative programming on method/algorithm level.

But I want to share a declarative coding style that is applicable also on higher levels. It requires some out-of-the-box thinking.

Real world snippet from a XMMP chat app:

This code lives in the User Interface and is executed once the send button is tapped. roothandler is a function/closure callback, which takes a Message parameter. Message is a nested enum with annotated model structs. The message in this example is Message.chat(.send(…)) with a ChatMessage struct as annotation. ChatMessage itself is an immutable struct. Necessary changes — like appending an image selected in UI — are reflected by calling alter and pass in a Change enum (very similar to a message) which will generate a new chat message reflecting the changes.

ChatMessage.Change defines all interaction that are possible, in this case:

  • .wasReadBefore(Bool) indicates that this message might have been read by the User.
  • .add(.image(ChatImage?)) appends an image to the list of images, as we can see in the second case in alter(…). I allow an optional here, as this will reduce the code paths in the UI.
  • .remove(.image(ChatImage)) filters the image from the existing list. Seen in case three in alter(…).

The ChatMessage from above is a model type, representing a tiny part of the app state at a certain moment.

We can organise the whole AppState in an immutable struct, very similar to ChatMessage itself

Adding a new chat to the app’s state would look like

This AppState.Change DSL is the simplest one possible, as it just exposes the data fields that the user can change. The downside is that a we would write a very clunky term each time we want to add a simple data item.
But we can write the DSL as we like it, so it is easy to adapt it to allow this:

or

Definitively more declarative than with the first DSL. I encourage you, to go crazy and try things out, as the DSL is actually pretty easy to replace. And as it is defined as types, the compiler will tell you where to change stuff. How those more complex DSLs are designed is shown in the ChatMessage code.

Since AppState is immutable, it will be recreated each times a change needs to be reflected. There for we need an object that wraps the state and keeps the newest version — a `Store`.

But here I dont want to use a class, as it offers me more than I need, what I would classify as accidental complexity — or: “Where the bugs live”.

As we will later see, we actually could use a combination of three methods to create a reference type, that we could use as the app store’s store, but it would pose quite some challenge to request something from it. Either we would have to hand over callback, which would clutter up the code, or we would have to use a some form of builder pattern and create an accessor function each time we want to access it values via it returning.

Instead I use a tuple of functions. The tuple itself if a value type, but since all tuple member will be functions (reference type), this doesn’t pose a problem.

Access returns the current state.
Change accepts AppState.Change values and forwards them to the state.
Reset resets the state with a fresh AppState object. Callback implements a simple notification pattern. The store is used like:

Listen for store changes is easy as:

change : { state = state.alter($0) … and reset : { state = AppState() are the only places outside of the UI that require mutability.

Message is an app-wide type. It is used by the UI and the app's features, a kind of module that listens for every message and will react for those it is interested in.

f

Let’s look at some examples:

To log into the service, the UI might trigger this

If user taps on a button to subscribe to another user, it might look like the following

In UIKit

In SwiftUI

Our earlier example

I find this example especially intriguing, as in a single statement we do several things. But most of all: it comes quite close to natural language: “Chat, send chat message from user to receiver with text as body after adding the selected image”. Add another “Please” and it would be what I would say, if the Chat Feature was a human being instead.

Now that we have seen a model type, the message and how to use those together, let’s inspect how to handle those message.

The Message type we have seen above is shared among all features.
Again I have decided to neither use classes or structs as module types, simply because they offer too much stuff I don’t need.

Instead I use following construct:

createTodoListFeature is a partially applied function. It is not used very often in OO, even though it they are super versatile. You will find articles on the Internet that describe how partially applied functions can replace pretty much all OO-pattern in FP. But this is wrong: partially applied function violate the purity that FP requires from their functions.

But what is happening here?
createTodoListFeature is a function, that takes another function as a parameter — the output — and returns an instantiated but not executed function.
This function is an Input type. We can assign this function to a variable or put it into an array and pass in every Message we receive. We can say that the Input function itself is the feature.
In createTodoListFeatures body we instantiate the UseCases which this feature uses.
The UseCases are modules that work a little different than those we have seen before. I have taken those UseCases from Robert C. Martins "Clean Architecture". You will find a lot of information on them in the internet.

UseCases are “PATs” — Protocol and Associated Types

and are implemented like

A UseCase is meant to do one thing, there for its Request type often will just have one case. The Response type often will have more that one case, for success, failure or in scenarios where different things might be returned. Why do I use PATs here? because they allow me to bundle the use case with it request and response. There for I chose a struct here, even though it offers more than I need. Note, that the Interactor itself is private: We achieved perfect black-boxing. And it is a class, so you might apply any OO style here that you prefer. Also this ensures that we can connect to libraries and API’s. Robert C. Martin uses Gateways in his clean architecture’s use cases. I want to point out that store is such a gateway in this example.

I call all features together the “AppCore”, let’s see how it is created:

createAppCore works like the create<Feature> functions. It returns an Input function which will only iterate over all receivers and features and forward each message. This function is used like:

ViewState is a receiver (and will be informed before any feature) and an ObservableObject and as such accessible in SwiftUI.

In UIKit I found this construct to to the job:

Now you just subclass each ViewController from ViewStateViewController and if a new state is available, handle(changed viewState: ViewState) will be triggered.

We completed the circle. We started by sending Messages from the UI, now we are updating it for any state change.

At this point I want to link to Primitivo, a Framework that allowas declarative programming in UIKit.

Ok, that was a wild ride. I tried to keep this article brief yet clear. I am not sure, if I managed to do that.

  • Unidirectional Flow
  • Fully decoupled UI
  • The AppCore doesn’t change during runtime
  • Super easy and fun to test.

These are all highlights that I couldn’t elaborate on in this article. But I have also written a series about the Core|UI architecture, which explains all this in more detail. It also comes with an example project, that can be found on gitlab.

Freelance Software Developer and Code Strategist

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store