Swift Talk # 378

## Structural Programming: Introduction

### This episode is freely available thanks to the support of our subscribers

We introduce a concept called structural programming and take the first steps toward an implementation.

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 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 {
}
}
``````

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 {
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
}
}
``````

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.

### Resources

• #### Sample Code

Written in Swift 5.9

### Swift, the Language

57 Episodes · 20h06min

See All
SwiftUI

### Particle Effects (Part 3)

Episode 420 · Sep 13

SwiftUI

### Particle Effects (Part 2)

Episode 419 · Sep 06

SwiftUI

### Particle Effects (Part 1)

Episode 418 · Aug 30

SwiftUI

### Lazy Container Views (Part 4)

Episode 417 · Aug 16

SwiftUI

### Lazy Container Views (Part 3)

Episode 416 · Aug 09

SwiftUI

### Lazy Container Views (Part 2)

Episode 415 · Aug 02

SwiftUI

### Lazy Container Views (Part 1)

Episode 414 · Jul 26

SwiftUI

### Bento Layout (Part 4)

Episode 413 · Jul 19

Unlock Full Access

## Subscribe to Swift Talk

• ### Watch All Episodes

A new episode every week