00:06 Today we'll be writing a structural programming library. Structural
programming refers to the idea of writing programs on top of the structures of
data types. Features in Swift that use structural programming are Codable,
which allows us to write serialization based on the structure of a type;
Mirror, which we use to dynamically introspect values; and most recently
added, macros.
00:36 Writing a macro means a lot of typing and diving deep into the
syntax tree. This only allows us to write certain kinds of programs on the
structures of enums and structs. The library we want to write is much simpler;
we'll convert a struct into a structural representation of its properties, and
then we'll write algorithms on that structural representation.
Structure of a Struct
01:18 Let's first create a sample model — a Book struct with two
properties:
import Foundation
struct Book {
var title: String
var published: Date
}
01:30 In another file, we start writing our library with types that can
represent the structure of Book. The first part of the library is a Struct
type, which can hold a struct's name and some generic value for its properties:
import Foundation
struct Struct<Properties> {
var name: String
var properties: Properties
}
01:48 Concretely, the Properties parameter will be a list of
Property values:
struct Property<Value> {
var name: String
var value: Value
}
02:08 The structure of Book can be represented by a Struct with the
name "Book" and, for each of its properties, a Property with this name as a
string. What we're missing is a way to combine the properties into one type.
Modeling this as an array isn't an option, because then the Value parameter of
all properties would have to be the same type.
02:40 Instead, we'll use a linked list to create a type-safe
representation of differently typed properties:
struct List<Head, Tail> {
var head: Head
var tail: Tail
}
02:56 We're now almost ready to define the structure of Book. We add a
type alias called Structure to the Book struct, and we assign a Struct
with a list of properties. The list's head is a Property<String>. The tail is
another list, whose head is a Property<Date>, and we don't yet know what the
tail of this nested list should be, so let's just use Int as a placeholder
type:
struct Book {
var title: String
var published: Date
typealias Structure = Struct<List<Property<String>, List<Property<Date>, Int>>>
}
03:38 Instead of Int, we need something to indicate that there's
nothing there — a sentinel value. For this, we write an Empty type, which will
solely be used to represent the end of a list:
struct Empty { }
struct Book {
var title: String
var published: Date
typealias Structure = Struct<List<Property<String>, List<Property<Date>, Empty>>>
}
03:58 Next, we need a way to convert a Book value into this structural
representation and a way to create a Book from that representation. We add a
computed property to convert a value to a Structure. Because Swift already
knows the structure is a Struct, we can write .init, and it automatically
completes the correct initializer for us. We pass in "Book" for the struct's
name. For the properties, we write .init again to let the compiler add stubs
for the list's head and tail. The list's head should hold the book's title
property, which we initialize with the "title" name and the book's title as
the value. The tail is another list, with the published property as its head
and an Empty value as its tail:
struct Book {
var title: String
var published: Date
typealias Structure = Struct<List<Property<String>, List<Property<Date>, Empty>>>
var to: Structure {
.init(name: "Book", properties: .init(head: .init(name: "title", value: title), tail: .init(head: .init(name: "published", value: published), tail: .init())))
}
}
05:41 It's a bit of a hassle to type out this structure, but later on,
we'll write a macro to automatically generate the Structure type alias, the
to property, and the from function we need to create a Book value from a
structural representation:
struct Book {
static func from(_ s: Structure) -> Self {
}
}
06:05 Inside from, we want to construct a Book and pull values for
its properties out of the s structure. The title value can be found in the
property list's head, and for the publish date value, we go into the list nested
in the tail, and we take the value of the nested list's head:
struct Book {
var title: String
var published: Date
typealias Structure = Struct<List<Property<String>, List<Property<Date>, Empty>>>
var to: Structure {
.init(name: "Book", properties: .init(head: .init(name: "title", value: title), tail: .init(head: .init(name: "published", value: published), tail: .init())))
}
static func from(_ s: Structure) -> Self {
.init(title: s.properties.head.value, published: s.properties.tail.head.value)
}
}
06:42 If there'd be a third property, we'd read from
s.properties.tail.tail.head.value — we just keep appending .tail to go
deeper into the linked list.
Displaying Structures
06:56 Now we can write a generic algorithm on the structure — for
example, one that converts a Struct into a SwiftUI View. This could be
useful as a debugging tool that lets us display a struct value in a debug
console in our app. By writing the algorithm for the structural representation,
we can make this algorithm work for Book values, but also for any other
structure.
07:36 In a new file, we import SwiftUI, and we can create a protocol
called ToView. Types conforming to this protocol need to define a view
property that returns some view:
import SwiftUI
protocol ToView {
associatedtype V: View
var view: V { get }
}
07:59 This protocol is literally the definition of View, but with
different names, but by using a custom protocol, we get to write our own
conformances. For example, Empty can conform by just returning an EmptyView:
extension Empty: ToView {
var view: some View {
EmptyView()
}
}
08:29 We can conform Property as long as its Value type also
conforms to ToView. The view it returns can be a LabeledContent with the
property's name as the label and its value's view as the content:
extension Property: ToView where Value: ToView {
var view: some View {
LabeledContent(name) {
value.view
}
}
}
09:11 List conditionally conforms to ToView if both its Head and
Tail types conform. For its view, we could use a VStack to lay out the head
and tail views, but it'll be more flexible if we just return a group of views
and let the caller of the view property decide what their container should be:
extension List: ToView where Head: ToView, Tail: ToView {
var view: some View {
head.view
tail.view
}
}
To return the views separately, we need to mark the view property as being a
view builder. And by doing this in the protocol, we give every conforming type
this capability:
protocol ToView {
associatedtype V: View
@ViewBuilder var view: V { get }
}
10:04 Then there's only Struct left. To construct its view, we can
return a VStack with the struct's name in bold, followed by the properties:
extension Struct: ToView where Properties: ToView {
var view: some View {
VStack {
Text(name).bold()
properties.view
}
}
}
Displaying Book Struct
10:30 In ContentView, we create a sample Book value, and we try to
display its structure:
struct ContentView: View {
var book = Book(title: "Thinking in SwiftUI", published: .now)
var body: some View {
book.to.view
.padding()
}
}
10:51 This doesn't yet compile and the warnings are clear: the ToView
conformance of Property dictates that we conform both Date and String to
ToView. So, we go back to our ToView file and add conformances for the
primitive types. For String, we can return a Text view containing the string
itself:
extension String: ToView {
var view: some View {
Text(self)
}
}
11:38 And for Date, we can use the Text initializer that takes a
Date and a format:
extension Date: ToView {
var view: some View {
Text(self, format: .dateTime)
}
}
11:58 Now our code compiles, and when we run it, we see a view
describing our Book value and listing the name of the struct and the name and
value of each property:

12:13 We've now created a rather complicated way to display this tiny
bit of information. But next time, we'll work on a macro that generates the
structural representation of a Struct. That means we only have to add another
property to Book, and the displayed structure will automatically change to
include that new field.