This post is just one in a series that aims to cover the foundations of server-side Swift from first principles. Please consider checking out the rest in the series:

Last time we defined a DSL in Swift for creating HTML documents. We accomplished this by creating some simple value types to describe the domain (nodes and attributes) and some helper functions for generating values. In the end it looked like this:

header([
h1(["id" => "welcome"], ["Welcome!"]),
p([
"Welcome to you, who has come here. See ",
a(["href" => "/more"], ["more"]),
"."
])
])


to generate this HTML:

<header>
<h1 id="welcome">Welcome!</h1>
<p>
Welcome to you, who has come here. See <a href="/more">more</a>.
</p>


Now we are going to tackle a problem that goes up one layer in the web-server request lifecycle: creating views. These are the things responsible for taking some data, say a list of articles, and creating the HTML to represent that data. It may sound easy, but there are a lot of difficult problems to solve with views. We want our views to be composable so that we can create small sub-views focused on rendering one piece of data and be able to reuse it. We also want our views to be flexible enough for future developments that are hard to see right now.

The View Function

Like most topics discussed on this site, we are going to define view as a plain ole function. Also, like most topics in computer science, a monoid is involved. Luckily we’ve talked about these topics a lot on this site (here and here), and so there’s a lot of material to pull from. We will assume you are familiar with most of this content, and quickly recap the code that defines these objects:

precedencegroup Semigroup { associativity: left }
infix operator <>: Semigroup

protocol Semigroup {
static func <>(lhs: Self, rhs: Self) -> Self
}

protocol Monoid: Semigroup {
static var e: Self { get }
}

extension Array: Monoid {
static var e: Array { return  [] }
static func <>(lhs: Array, rhs: Array) -> Array {
return lhs + rhs
}
}


Here we have defined two protocols that define what a semigroup and monoid are, and made Array conform because that is the monoid we are going to be most interested in for this article.

We are going to define a view, roughly, as a function from some data to an HTML node (which we defined last time). It turns out that it helps to actually map into an array of nodes, [Node], since that aids composition by stacking views on top of each other. It also helps to generalize [Node] to be any monoid, not just [Node]. We will do this by making a struct that wraps a function:

struct View<D, N: Monoid> {
let view: (D) -> N

init(_ view: @escaping (D) -> N) {
self.view = view
}
}


All of our views will be mapping into [Node], but that lil extra bit of generality will pay dividends later. With this simple type we can cook up a few views. Below I have roughly recreated some of the HTML on this very site:

let headerContent = View<(), [Node]> { _ in
[
h1(["Few, but ripe..."]),
a([href => "/hire-me"], ["Hire Me"]),
a([href => "/talks"], ["Talks"])
])
]
}

struct FooterData {
let authorName: String
let email: String
}

let footerContent = View<FooterData, [Node]> {
[
ul([
li([.text($0.authorName)]), li([.text($0.email)]),
li([.text("@\($0.twitter)")]), ]), p(["Articles about math, functional programming and the Swift programming language."]) ] }  Notice that the header view has a () data parameter because it doesn’t need any data to construct its nodes. The footer however does need some data, which we packaged up into a struct. Let’s also make a view that renders a list of articles: struct Article { let date: String let title: String // more fields, e.g. author, body, categories, ... } let articleListItem = View<Article, [Node]> { article in [ li([ span([.text(article.date)]), a([href => "#"], [.text(article.title)]) ]) ] } let articlesList = View<[Article], [Node]> { articles in [ ul( articles.flatMap(articleListItem.view) ) ] }  Note that we first created a helper view articleListItem for rendering a single item, and then we used it to render the full list of articles. We have to flatMap onto articleListItem.view since it returns an array of nodes. We can now bring this all together to create a homepage view that composes these views together. We will create a HomepageData struct to bundle up all of the data the page needs (for both the articles and footer). struct HomepageData { let articles: [Article] let footerData: FooterData } let homepage = View<HomepageData, [Node]> { [ html( [ body( [ header(headerContent.view(())) ] + [ main(articlesList.view($0.articles)) ]
+ [ footer(footerContent.view($0.footerData)) ] ) ] ) ] }  It isn’t the prettiest code, but we’ll make it better soon. And it’s not that bad right now! Some things to note: • It’s just a pure function mapping an array of articles to some HTML nodes, which can be rendered to a string and then tested in a unit test. • We got to leverage Swift features to aid in composition. For example, here we used array concatenation with + to stack our three views on top of each other. It doesn’t look great right now because we had to wrap each of our subviews in semantic tags. • We were able to use subviews to clean up this view and make it clear that we are just stacking a header on top of main content on top of a footer. We can take the homepage for a spin by creating some homepage data and rendering. The render(node:) function we made last time only works on nodes, not views. We can write a render for views like so: func render<D>(view: View<D, [Node]>, with data: D) -> String { return view.view(data) .map(render(node:)) .reduce("", +) }  And now creating some data and rendering looks like this: let data = HomepageData( articles: [ Article(date: "Jun 22, 2017", title: "Type-Safe HTML in Swift"), Article(date: "Feb 17, 2015", title: "Algebraic Structure and Protocols"), Article(date: "Jan 6, 2015", title: "Proof in Functions"), ], footerData: .init( authorName: "Brandon Williams", email: "mbw234@gmail.com", twitter: "mbrandonw" ) ) render(view: homepage, with: data)  which will output the following HTML. <html> <body> <header> <h1>Few, but ripe...</h1> <menu> <a href="/about">About</a> <a href="/hire-me">Hire Me</a> <a href="/talks">Talks</a> </menu> </header> <main> <ul> <li><span>Jun 22, 2017</span><a href="#">Type-Safe HTML in Swift</a></li> <li><span>Feb 17, 2015</span><a href="#">Algebraic Structure and Protocols</a></li> <li><span>Jan 6, 2015</span><a href="#">Proof in Functions</a></li> </ul> </main> <footer> <ul> <li>Brandon Williams</li> <li>mbw234@gmail.com</li> <li>@mbrandonw</li> </ul> <p>Articles about math, functional programming and the Swift programming language.</p> </footer> </body> </html>  This is complicated enough view that was simple to compose from smaller pieces! We could stop here and we would have a nice model for creating views, but there is so much more we can do. We need to explore the ways that views can be composed to unlock the next benefits of views… View Composition Since View is a function, we would expect that there are some nice ways to compose them. And indeed, there are at least 3 ways! View Composition #1 – map The first type of composition we will discuss is called map. It is closely related to Array’s map, so let us recall that definition. Array<A> has a method called map that takes a function f: (A) -> B and returns an Array<B>. You can think of f: (A) -> B as transforming Array<A>s to Array<B>s by just applying f to each element of the array. View<D, N> also has such a function, but it transforms the N part of the view. Let us first write the signature of how such a function would look: extension View<D, N> { func map<S>(_ f: @escaping (N) -> S) -> View<D, S> { ??? } }  We know that this method returns a View<D, S>, which is really just a function (D) -> S, so we can fill in a bit of this function body: extension View<D, N> { func map<S>(_ f: @escaping (N) -> S) -> View<D, S> { return View<D, S> { d in ??? } } }  Now we have at our disposal d: D, f: (N) -> S, and self.view: (D) -> N. Seems like we can just compose these two functions and feed d into em: extension View<D, N> { func map<S>(_ f: @escaping (N) -> S) -> View<D, S> { return View<D, S> { d in f(self.view(d)) } } }  This function allows us to perform a transformation on a view’s nodes without knowing anything about the data that is involved. We already did something like this (3 times) when we wrapped our subviews in semantic tags, for example: [ footer(footerContent.view($0.footerData)) ]


If we wanted to store this in another view, it would be a bit cumbersome in the naive way:

let siteFooter = View { [ footer(footerContent.view($0)) ] }  Our function map allows us to do this quite succinctly: let siteFooter = footerContent.map { [footer($0)] }


Even better, if we take a few nods from Haskell, PureScript and other purely functional languages, we could rewrite this as:

precedencegroup ForwardComposition { associativity: left }
infix operator >>>: ForwardComposition

// Left-to-right function composition, i.e. (f >>> g) = g(f(x))
func >>> <A, B, C>(f: @escaping (A) -> B,
g: @escaping (B) -> C) -> (A) -> C {
<> siteFooter.contramap { $0.footerData }  This allows views to take only the data they need to do their job, while remaining open to being plugged into views that take more data. Bringing it all together We can now use all of our new tools of composition to refactor our view of articles into a short and sweet code snippet. Let’s first remind ourselves of all the atomic units of views we have at our disposal: let headerContent = View<(), [Node]> { _ in [ h1(["Few, but ripe..."]), menu([ a([href => "/about"], ["About"]), a([href => "/hire-me"], ["Hire Me"]), a([href => "/talks"], ["Talks"]) ]) ] } let footerContent = View<FooterData, [Node]> { [ ul([ li([.text($0.authorName)]),
li([.text($0.email)]), li([.text("@\($0.twitter)")]),
]),
p(["Articles about math, functional programming and the Swift programming language."])
]
}

let articleCallout = View<Article, [Node]> { article in
[
span([.text(article.date)]),
a([href => "#"], [.text(article.title)])
]
}

let articlesList = View<[Article], [Node]> { articles in
[
ul(
articles.flatMap(articleCallout.view >>> li >>> pure)
)
]
}


From these pieces we want to create a View<HomepageData, [Node]> that assembles all the pieces and wraps it all in html and body tags. One approach would be to just take what we’ve done so far to combine the header/content/footer into a view, and then map on it for the enclosing tags. Remember that we also have to map on each of our subviews to properly enclose them in their semantic tags:

let homepage: View<HomepageData, [Node]> =
(
<> articlesList.map(main >>> pure).contramap { $0.articles } <> footerContent.map(footer >>> pure).contramap {$0.footerData }
)
.map { nodes in
[
html(
[
body(nodes)
]
)
]
}


This looks quite nice! However, we probably want to render the header and footer on most pages, and be able to just plug in the middle content on a page-by-page basis. We may want to also render additional stuff into the pages, like stylesheets, javascripts and meta tags. Rails provides something called “layouts” to solve this in a reusable way, but we can just use a plain ole function!

// A struct to hold all the data that the layout needs, in addition
struct SiteLayoutData<D> {
let contentData: D
let footerData: FooterData
}

func siteLayout<D>(content: View<D, [Node]>) -> View<SiteLayoutData<D>, [Node]> {
return (
<> content.map(main >>> pure).contramap { $0.contentData } <> footerContent.map(footer >>> pure).contramap {$0.footerData }
)
.map(body >>> pure >>> html >>> pure)
}


We got a little fancy in that last line by adding the body and html tags all in one mapping, but it still reads quite well! And now this can be used to render our list of articles very easily!

let data = SiteLayoutData(
contentData: [
Article(date: "Jun 22, 2017", title: "Type-Safe HTML in Swift"),
Article(date: "Feb 17, 2015", title: "Algebraic Structure and Protocols"),
Article(date: "Jan 6, 2015", title: "Proof in Functions"),
],
footerData: .init(
authorName: "Brandon Williams",
email: "mbw234@gmail.com",
)
)

render(view: siteLayout(content: articlesList), with: data)


This generates the exact same HTML from the beginning of this article, but using very small atomic pieces that plug together in beautiful ways.

Conlusion

We have now seen that by embracing views as simple, pure functions from data to nodes we are able to discover 3 different forms of composition: map, contramap and monoid append. These operations are either completely hidden or obfuscated from you in the templating language world. For example, the only way to do a map on a view is to create a whole new template file to enclose the existing view. And amazingly, these 3 simple compositions subsume all possible ways of combining views in templating languages, such as partials, layouts, yield blocks, collections, nested layouts, etc…!

Believe it or not, there is still at least one more type of composition that can be done on views. It’s called flatMap and it’s useful for when you need to combine two views in a more complicated way than stacking them. We’ll save that for a future article. Until then, checkout this playground of all the work we have done.

Exercises

1.) Define flatMap on View<D, N>:

extension View<D, N> {
func flatMap<S>(_ f: @escaping (N) -> View<D, S>) -> View<D, S> {
???
}
}


2.) Explore the idea that “layouts”, as defined by Rails, is just a function between (View<D, N>) -> View<S<D>, N>, where S<D> is some type generic of your data D that adds whatever data is required by the layout. How do these layout functions compose?

3.) Implement the following function:

func divide<A, B, C, N: Monoid>(_ f: @escaping (A) -> (B, C))
-> (View<B, N>)
-> (View<C, N>)
-> View<A, N> {

???
}


This allows you to take a function that splits a piece of data into two smaller pieces ((A) -> (B, C)), then take two views on the smaller pieces of data, and glues them together. This can also be achieved with contramap and <>, but this is a slightly shorter way of doing it. Also, this function can be seen as the contravariant analogue to applicative functors.

4.) Implement the following function:

enum Either<A, B> {
case left(A)
case right(B)
}

func choose<A, B, C, N>(_ f: @escaping (A) -> Either<B, C>)
-> (View<B, N>)
-> (View<C, N>)
-> View<A, N> {

???
}


This allows you to choose between what views to use based on a function (A) -> Either<B, C>.This function can be seen as the contravariant analogue to alternative functors.

A little bit of math…

I would be remiss if I didn’t take a brief moment to mention a bit of mathematical jargon so that you can research some of these topics more deeply. The fact that View<D, N> has a map on it means that View is a functor in the type parameter N. You are already familiar with some functors in Swift, like Array<A> and Optional<A>.

The fact that View<D, N> has a contramap on it means that View is a contravariant functor in the type parameter D. If you read our previous article on predicates and sorting functions you would have seen us define Predicate<A> and Comparator<A>. Both of those types are contravariant functors.

And finally, the fact that View<D, N> is a functor in N and a contravariant functor in D at the same time, makes View a profunctor.