Video #19: Algebraic Data Types: Generics and Recursion
Episode: Video #19 Date: Jun 11, 2018 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep19-algebraic-data-types-generics-and-recursion

Description
Our third installment of algebraic data types explores how generics and recursive data types manifest themselves in algebra. This exploration allows us to construct a useful, precise type that can be useful in everyday programming.
Video
Cloudflare Stream video ID: f48956c0b9ebbbfe36f9bbf382e9c8df Local file: video_19_algebraic-data-types-generics-and-recursion.mp4 *(download with --video 19)*
Transcript
— 0:05
We’ve now had two episodes dedicated to exploring the relationship between algebra and the Swift type system. Let’s do a quick recap:
— 0:15
In the first episode we showed that addition and multiplication that we all know from algebra have a very real and direct correspondence to enums and structs in Swift. Specifically, a struct is like the product of all the types inside, and an enum is like the sum of all the types inside. This had a very real world application in allowing us to refactor data types so that values that we know should not be possible to exist are provably unrepresentable by the compiler.
— 0:37
Then, in the second episode, we showed that exponentials that we all know from algebra, that is take b to the a power, directly corresponds to functions from a type A into a type B . This was amazing because we could then take all the properties we know about exponentials and transport them over to the world of types. This allowed us to understand how making small changes to the inputs and outputs of a function affect the overall complexity of the function.
— 1:07
Well, today we are exploring the next piece of bridging algebra to Swift types. We are going to explore how generics and recursive data types fit into algebra. Turns out, like most things we do on this show, there is a very beautiful interpretation of these things in the algebra world. And we get to use a whole body of knowledge from algebra in order to better understand the Swift type system.
— 1:31
Let’s get started by looking at generics! Generics as function
— 1:36
Over the course of our series we’ve defined the following types: enum Either<A, B> { case left(A) case right(B) } struct Pair<A, B> { let first: A let second: B } struct Func<A, B> { let apply: (A) -> B }
— 2:10
These three types represent the three types of algebra that we’ve explored so far.
— 2:15
And we even became comfortable with using a shorthand notation for taking the sum and product of types: Either<A, B> = A + B Pair<A, B> = A * B Func<A, B> = B^A
— 2:48
Using this shorthand, even though it is not valid Swift code, allowed us to perform some high level algebraic manipulations to the symbols abstractly, and then we transported the results of that back to types. For example, we saw that we could start with a complicated type and refactor it using algebra to get something simpler: Either<Pair<A, B>, Pair<A, C>> = Pair<A, B> + Pair<A, C> = A * B + A * C = A * (B + C) = Pair<A, B + C> = Pair<A, Either<B, C>>
— 3:47
This means an either of pairs where the pairs share a type is really just a pair with an a single either.
— 4:03
Now let’s looking at those lines we wrote earlier: Either<A, B> = A + B Pair<A, B> = A * B Func<A, B> = B^A There’s something kinda funny here. It kinda looks like the types are “functions”. In fact, we change the angled brackets for parentheses we see: Either(A, B) = A + B Pair(A, B) = A * B Func(A, B) = B^A
— 4:21
And now they really look like functions. It’s kinda like Swift types that have generics are functions that take in types and spit out all new types. This is precisely the relationship between generics and algebra. Functions in the algebra world correspond to generic types in the Swift world.
— 4:45
Now, let’s not get confused here. In the last episode on algebraic data types we showed that Swift functions correspond to exponentials in algebra. We are saying something different here. We are saying that Swift’s generic types correspond to functions in the algebra world. It’s a lil bit higher level that what we have previously seen. // | Algebra | Swift Type System | // | ------------ | ----------------- | // | Sums | Enums | // | Products | Structs | // | Exponentials | Functions | // | Functions | Generics |
— 5:33
What are some other examples? Well, Optional is a generic type. enum Optional<A> { case some(A) case none }
— 5:48
So we can write: Optional(A) = A + Void = A + 1 A? = A + 1
— 6:10
So, Optional is kinda like a type-level function that adds 1 to any type, where 1 represents that additional some case.
— 6:14
We can even compose the type-level functions to get new functions, like if we composed Optional with Pair : Pair(Optional(A), Optional(B)) = A? * B? = (A + 1) * (B + 1) = A*B + A + B + 1
— 6:51
So here we have seen that a pair of optionals really leads to sum type with four cases! This is why languages that do not have proper sum types tend to have much more complicated types than necessary. A pair of optionals is the closest you can get to a sum of types in a language without sum types, and we see here there are two extra terms hanging around that we don’t want representable: the A*B and the 1 .
— 7:15
Now the cool thing about interpreting generics as functions in algebra is that we can manipulate an algebraic expression in ways that are completely valid algebraically, yet seemingly nonsensical in the type world. But we can just keep making the manipulations and see where we end up algebraically, and then transport it back to types and see if see if we can make sense of it in the type world too. This is a very strange thing.
— 7:49
In order to see this we need to explore the world of recursive data types! Recursion in data types
— 7:57
What is a recursive data type? It’s a type where one of its parts refers to the whole of the type. This is always an inductive process wherein we identify a base value to start from, and then define all other values as a combination of “smaller” or “previous” values.
— 8:20
Let’s start very simple. In math, the natural numbers are all whole numbers greater than or equal to zero: 0, 1, 2, 3, etc. There is a very natural, inductive way to describe all these numbers. // A natural number is either: // - Zero, or // - The successor to some other natural number
— 8:49
Notice how this definition is recursive. In order to define a natural number we must use a natural number in the definition! However, this isn’t a problem because we reference a “smaller” natural number (the predecessor), and we describe a base case of zero. For example:
— 9:07
3 is a natural number because it the successor of 2 and 2 is a natural number because it is the successor of 1 and 1 is a natural number because it is the successor of 0 and 0 is a natural number because we say so in our base case.
— 9:19
The amazing thing is that we can translate this English language description of the natural numbers into a Swift type, and it will precisely describe the natural numbers. So, the sentence started with an “either” statement, so that must mean we are dealing with an enum, so we can start there: // A natural number is either: // - Zero, or // - The successor to some other natural number enum NaturalNumber { <#???#> }
— 9:39
There were two cases considered in the statement. The first case is the description of the base case, which is just a value called “zero”. Well, adding that case to this enum corresponds directly to adding a value to the type, which we will call zero: // A natural number is either: // - Zero, or // - The successor to some other natural number enum NaturalNumber { case zero <#???#> }
— 9:49
The second statement is the recursive one. It says that if we are not zero, then we must be the successor of some natural number. This translates into a case of the enum whose associated value references the whole: // A natural number is either: // - Zero, or // - The successor to some other natural number enum NaturalNumber { case zero case successor(NaturalNumber) }
— 10:11
Now when build this we get a compile error. Recursive enum ‘NaturalNumber’ is not marked ‘indirect’ Swift detects recursive data types and makes us explicitly note this in our definition. enum NaturalNumber { case zero indirect case successor(NaturalNumber) }
— 10:36
OK, this type is super simple, but what does it mean? Let’s construct some values! let zero = NaturalNumber.zero let one = NaturalNumber.successor(zero) let two = NaturalNumber.successor(one) let three = NaturalNumber.successor(.successor(.successor(.zero)))
— 11:30
So we see we can easily construct natural numbers by just taking the successor of some existing natural number. This is pretty verbose, but the compiler is helping us constructing these and preventing us from constructing any invalid values. For example, this type is not the same as the unsigned integer type UInt in Swift. The UInt type is more of an aspirational model for the non-negative integers, and it is very error prone. It can very easily to a runtime crash: var x: UInt = 0 x - 1 Execution was interrupted, reason: EXC_BAD_INSTRUCTION
— 12:13
Swift decides to crash because otherwise it would instead need to rollover to the maximum UInt value, which is what most languages do by default and can be very dangerous! If you want to avoid a runtime crash you can use an explicit overflow operator to allow this behavior: x &- 1 // 9223372036854775807
— 12:32
But now you run the risk of introducing very strange values into your programs.
— 12:48
This, however, is completely impossible to recreate with NaturalNumber . If we wanted subtract one from a natural number the compiler is going to force us to make it very clear how we handle subtracting from zero: func predecessor(_ nat: NaturalNumber) -> NaturalNumber { switch nat { case .zero: <#???#> case .successor(let predecessor): return predecessor } }
— 13:24
What can we do with the zero case? We can either decide to crash or we can change the function to return an optional and return nil there. It’s amazing that the compiler is forcing us to consider this, for this will not even compile until we fill in this case. There is no equivalent to this when working with UInt .
— 14:15
Now let’s look at this type algebraically. Using our shorthand notation we can translate the NaturalNumber enum into the following algebra: NaturalNumber = 1 + NaturalNumber = 1 + (1 + NaturalNumber) = 1 + (1 + (1 + NaturalNumber)) = 1 + (1 + (1 + (1 + NaturalNumber))) … = 1 + 1 + 1 + 1 + 1 + …
— 15:03
It’s kinda like the NaturalNumber type is just an infinite sum of 1 s, which each instance of a 1 corresponds to a natural number. This of course doesn’t make sense algebraically, but we can still mess around with the symbols just to ponder! It’s almost as if NaturalNumber is an enum with infinite cases where each case represents every possible natural number. Now we can’t write an enum with an infinite number of cases, but the algebra has shown us that this is kind of what’s going on. Recursion in data types with generics
— 15:45
OK, the NaturalNumber type was a nice example to get our feet wet, but time to jump into the depths of algebra head first. Let’s see what recursive data types look like with generics.
— 15:52
Let’s consider the type of finite lists of values. Now, Swift already has a type for this called Array , and it’s a pretty well-oiled machine that is very performant. It’s a fool’s errand to try to make a more performant list type than what Array provides us. But also, its implementation is really complicated. If you go look at the source in Swift’s open source repo you will see that it’s seriously complicated, and I have a lot of trouble making heads or tails of it in code. Let’s see how simple the list type could be if we built it off the principles of algebraic data types.
— 16:35
So first, what is a list? Well, it’s a generic type List<A> , and it is recursively defined in a very similar way that NaturalNumber was defined. // A value in List(A) is either: // - the empty list, or // - a value (called the head) // appended onto the rest of the list (called the tail)
— 17:14
We can translate this directly into an enum: enum List<A> { case empty indirect case cons(A, List<A>) } The name cons for the second case is a bit of a term of art from Lisp meaning “construct”. To cons a value onto a list simply means to append it to the list.
— 17:58
We can easily _cons_truct some values: let xs: List<Int> = .cons(1, .cons(2, .cons(3, .empty))) // [1, 2, 3]
— 18:15
This type is a very precise description of what it means to have a finite collection of values. To work with this type you will always be switch ing on it and recursively handling the head and tail of the cons case until you get down to the base empty list case.
— 18:35
For example, if we wanted to sum a list of integers: func sum(_ xs: List<Int>) -> Int { switch xs { case .empty: return 0 case let .cons(head, tail): return head + sum(tail) } } sum(xs) // 6
— 19:28
This list type is kind of interesting, especially when comparing it to NaturalNumber and UInt . The corresponding type in the C world is the
NULL 20:37
Our List type doesn’t have any of those problems. We have the compiler stopping us the moment we encounter the empty case. There is idea of accessing an index beyond the end. The type is describing precisely the values it can hold and nothing more.
NULL 21:00
Now let’s try to understand what this type means algebraically. We can start by naively using our shorthand notation: // List(A) = 1 + A * List(A) We can think of 1 as the empty case, and A * List(A) as the cons .
NULL 21:24
So algebraically List is like a function that calls itself in its implementation. Since we are living in the algebra we can do things that don’t make sense for types. Like, I’m going to solve this equation for List so that we can get rid of the recursive nature of it: // List(A) = 1 + A * List(A) // => List(A) - A * List(A) = 1 // => List(A) * (1 - A) = 1 // => List(A) = 1 / (1 - A)
NULL 22:25
OK, in algebra everything here is totally fine, but in types it’s completely nonsensical: what does it mean to subtract or divide in the type system? But the interesting thing here is that List(A) is no longer defined recursively. We found a non-recursive description of it, even though it’s nonsensical.
NULL 22:51
Maybe there’s another way to look at this. List(A) = 1 + A * List(A) = 1 + A * (1 + A * List(A)) = 1 + A + A*A * List(A) = 1 + A + A*A * (1 + A * List(A)) = 1 + A + A*A + A*A*A * List(A) = 1 + A + A*A + A*A*A + A*A*A*A * List(A) = 1 + A + A*A + A*A*A + A*A*A*A * …
NULL 24:24
OK, now that seems much simpler, but what does it mean? Well, we have somehow gotten the algebra to describe finite lists of values in A as an infinite sum of powers of A . This infinite sum is already pushing the limits of what we can reasonably talk about in algebra, but let’s even take it further and try to translate it back to Swift. So, we have a sum, and that means it’s an enum. But it’s an infinite sum, so that must mean an enum with infinitely many cases? Well, that isn’t possible, but let’s try filling in some cases to see what this means: enum AlgebraicList<A> { case _ case (A) case (A, A) case (A, A, A) case (A, A, A, A) … } None of these cases have names yet, but they represent lists of a fixed size, so we can name each case using the number of elements inside. enum AlgebraicList<A> { case empty case one(A) case two(A, A) case three(A, A, A) case four(A, A, A, A) … }
NULL 25:40
It looks like we are literally enumerating every possible size of list there is. The first case is the empty list, the next case is all the lists of size one, then the lists of size two, etc.
NULL 25:58
For example, if we want an AlgebraicList of size four: AlgebraicList<Int>.four(1, 2, 3, 4) Choosing a value in AlgebraList means to first identify what size your list is, choose the corresponding case, and then fill in all the values. AlgebraicList is totally an equivalent way of describing List<A> , it’s just that we can’t actually fully implement it due to it having infinitely many cases.
NULL 26:23
Now, before moving on, let’s go back to this non-recursive expression of List: List(A) = 1 / (1 - A)
NULL 26:33
This may look a little familiar if you were lucky or unlucky enough to have taken a calculus class or advanced algebra class you may recognize this as being related to “power series”.
NULL 26:47
Let’s take a quick trip over to Wikipedia to check something out . One of the examples on the page is the “geometric series” and we see something very similar: 1 / (1 - x) , and 1 + x + x^2 + x^3 + …, .
NULL 27:21
We’re seeing that algebraically, it’s totally reasonable to define lists using both of these formulas. List(A) = 1 / (1 - A) = 1 + A + A*A + A*A*A + …
NULL 27:44
It’s kinda wild that something like power series and all these weird ideas you may have learned in high school are somehow being applied to types. Makes you wonder what else can be applied! In a future episode we will use that formulation of List<A> to discover new and wonderful ways of traversing and updating the list in a very performant way! What’s the point?
NULL 28:31
Alright, it sounds like we’ll go deeper in the future and apply it to some real-world things, but in this episode we’ve done a ton of algebra and we haven’t really applied it at all. And this time it seems even more abstract than previous episodes. You say we’ll see something cool in a future episode, but you got nothing for now? So what’s the point? Are we ever going to need this?
NULL 28:57
We are! And just like how understanding sums and products allowed us to remove invalid states from our data types, understand generics and recursive data types allows us to do the same for more complicated types.
NULL 29:25
Let’s look at a real world example: how can we model a non-empty array type? This is a type that can never be empty. It must always contain at least one value. Why would we ever want this? Well, there are a lot of APIs out there, even in the standard library, that secretly should be working with non-empty arrays, but aren’t because the type is not available.
NULL 29:53
This includes: The [ group method], which traverses a sequence and collects adjacent, equatable elements together in nested arrays. These nested arrays can never be empty, but the method doesn’t do anything to eliminate that empty case. It would be nice if this API return an array of non -empty arrays instead.
NULL 30:40
Recently, Swift delivered a new API for randomness , and there was a particular discussion around methods that can vend a random element from an array. This API ends up returning an optional element to handle the empty case, but most of the time, at compile time, we’re dealing with a known number of elements, and a non-empty array type would improve this API for those cases.
NULL 31:16
There are a lot of APIs out there that demand a non-empty array, but we all just use regular arrays because that’s the tool we have at our disposal.
NULL 31:27
Now before we apply our knowledge of algebraic data types, let’s first take a look at a perfectly reasonable approach to trying to solve this without that knowledge.
NULL 31:38
Since many of us learned programming from the perspective of OOP and encapsulation, a totally reasonable way to approach the non-empty list type is to wrap an array inside another type as a private property, and then restrict access to this field via a controlled set of accessors and initializers. This is sometimes known as the “ proxy pattern ”. For example: struct NonEmptyArray<A> { private let values: [A] } By marking our values property private , we have completely blocked the outside world from modifying it.
NULL 32:21
Now, currently there’s no way to initialize this type since the field is private, so let’s provide an initializer. And this is where we can use encapsulation to enforce the contract of non-emptiness: init?(_ values: [A]) { guard !values.isEmpty else { return nil } self.values = values } We have a failable initializer to encode the fact that we require a non-empty array in order to properly initialize this type.
NULL 32:53
We can initialize a few values to see how it goes: NonEmptyArray([1, 2, 3]) // NonEmptyArray<Int> NonEmptyArray([]) // nil
NULL 33:10
It’s a lil annoying that we have to deal with an optional, but at least the type system is forcing us to consider the case that initializing failed instead of just hiding a runtime crash inside the initializer. We’re just gonna have to insert guard s or something into the places we want to initialize these things.
NULL 33:36
One thing we could do to improve is maybe use variadics! They allow us to use this ellipses ... syntax to splat a bunch of values into an array: init(values: A...) { self.values = values }
NULL 33:58
Now because we are using variadics we know we have to provide at least something in the initializer and so we can drop the failable initializer. Now we get to just do: NonEmptyArray(values: 1, 2, 3)
NULL 34:14
This makes creating non-empty arrays much more ergonomic and nice! However, it’s still difficult to use this type cause we can’t get at the elements, so to make it more familiar to use let’s make it conform to Collection : extension NonEmptyArray: Collection { var startIndex: Int { return self.values.startIndex } var endIndex: Int { return self.values.endIndex } func index(after i: Int) -> Int { return self.values.index(after: i) } subscript(index: Int) -> A { get { return self.values[index] } } } This is what the minimal conformance of Collection looks like, and we just proxy each method down to the values array. This is what the “proxy pattern” is all about!
NULL 34:58
Now we get to treat this type as a collection: NonEmptyArray(1, 2, 3).forEach { print($0) } // 1 // 2 // 3
NULL 35:12
Also, by virtue of conforming to collection we get a whole bunch of other methods and properties for free, like first : let ys = NonEmptyArray(1, 2, 3) ys.first + 2 Value of optional type ‘Int?’ not unwrapped
NULL 35:31
OK that doesn’t work because first returns an optional. But we know that our array is non-empty and so first should be non-optional! We can exploit a (optional magic) quirk of Swift that lets us actually provide a first that is not optional: extension NonEmptyArray { var first: A { return self.values.first! } } And we can force-unwrap here because we know that values is non-empty.
NULL 36:20
That ! is a lil scary, because it does mean there is the potential to someday crash. However, we are pretty strictly controlling the array we are dealing with, so it should be safe.
NULL 36:32
And now this works! ys.first + 2 // 3
NULL 36:38
What we’ve done here is wrapped a normal Swift array in a new type, but then restricted the ways in which you are allowed to create these values so that we could guarantee that the array is non-empty.
NULL 36:57
There’s only one problem. This is all completely wrong! It can be very tricky to control data in this way, and our implementation has a bug as a result.
NULL 37:16
Turns out, you can supply an empty list of variadics. It’s easy to make the incorrect assumption that variadics required we provide at least one value. So, we actually secretly have a runtime crash lurking in the shadows: NonEmptyArray<Int>()
NULL 37:36
This compiles! We were even able to omit the values label from before.
NULL 37:54
Now, when we call first , that exclamation mark that we were worried about becomes really worrisome. NonEmptyArray<Int>().first Unexpectedly found nil while unwrapping an Optional value
NULL 38:07
That’s kinda scary! The problem with this approach is that we are not leveraging the compiler to prove that the NonEmptyArray cannot possibly hold an empty array. Instead, we are enforcing that contract through good faith, that we promise to never add an initialize that would allow empty arrays in. so, we may “fix” the initializer cause that seems to be where the problem is: init(values first: A, _ rest: A...) { self.values = values }
NULL 38:43
And this causes our earlier instantiation to fail at compile time! Cannot invoke initializer for type ‘NonEmptyArray<Int>’
NULL 38:52
However, as we saw just now, it is easy for a mistake to slip in, and many months from now after we’ve forgotten some of the implementation details of this library we may accidentally sneak in another subtle change that breaks the contract. Like UInt , it’s more aspirational than anything else: we’ve baked in a contract and are hoping that no one will violate it.
NULL 39:31
However, if we had encoded the invariant of non-emptiness directly into the types, the compiler would be proving to us that this type cannot possibly hold an empty list. We wouldn’t have to be afraid of making subtle changes and breaking the contract. Let’s instead leverage our knowledge of algebraic, recursive, and generic data types to figure out how this type should really be defined.
NULL 39:39
Let’s first look at our algebraic definition of List<A> : List(A) = 1 + A + A*A + A*A*A + A*A*A*A + …
NULL 39:45
We want to make a NonEmptyList<A> type, and it’s basically the same as the List type except we wanna lop off that first 1 value: List(A) = 1 + A + A*A + A*A*A + A*A*A*A + … NonEmptyList(A) = A + A*A + A*A*A + A*A*A*A + …
NULL 40:04
So, how can we define this? There are two approaches we can take. We could start by factoring out an A from this expression: NonEmptyList(A) = A + A*A + A*A*A + A*A*A*A + … = A * (1 + A + A*A + A*A*A + A*A*A*A + …)
NULL 40:26
And now we have a product of two terms, A and an infinite sum of terms involving A . However, the sum of A ’s is precisely the definition of List<A> , and so we could define it as so: NonEmptyList(A) = A + A*A + A*A*A + A*A*A*A + … = A * (1 + A + A*A + A*A*A + A*A*A*A + …) = A * List(A)
NULL 40:44
Which translates into a struct quite easily: struct NonEmptyList<A> { let head: A let tail: List<A> }
NULL 41:05
And it is very clear that this indeed represents a list that can never be empty, for we always at least have the head value, even if the tail is empty. And we can use it easily: let zs = NonEmptyList(head: 1, .cons(2, .cons(3, .empty)))
NULL 41:28
And there we have it! A value that, at compile time, is guaranteed to be non-empty.
NULL 41:43
There’s another way we could have built a non-empty list type. By manipulating the algebra a little differently, we can come to a completely different definition.
NULL 41:53
An interesting thing about our current definition is that it’s not recursive. It relies on our previously-defined List type. Is it possible to maybe, instead, define NonEmptyList recursively?
NULL 42:08
Well, let’s take the algebra from before and this time factor an A out of the tail of the sum: NonEmptyList(A) = A + A*A + A*A*A + A*A*A*A + … = A + A * (A + A*A + A*A*A + A*A*A*A + …) This gives us a recursive definition! NonEmptyList(A) = A + A*A + A*A*A + A*A*A*A + … = A + A * (A + A*A + A*A*A + A*A*A*A + …) = A + A * NonEmptyList<A>
NULL 42:52
Now we have arrived at a definition of NonEmptyList that does not depend on List , and it directly translates into an enum: enum NonEmptyList<A> { case singleton(A) indirect case cons(A, NonEmptyList<A>) }
NULL 43:27
We can use it like so: let zs: NonEmptyList<Int> = .cons(1, .cons(2, .singleton(3)))
NULL 43:43
And this is pretty clear that NonEmptyList can never be empty for the base case of the recursion provides a single value. The smallest possible list must use the singleton case. let ws: NonEmptyList<Int> = .singleton(3) We can’t ever get the empty list!
NULL 44:12
Let’s see how the first property could be implemented on this type: extension NonEmptyList { var first: A { switch self { case let .singleton(first): return value case let .cons(head ,_): return head } } }
NULL 44:53
And now we pluck the first element off our zs and ws lists. zs.first // 1 ws.first // 3
NULL 45:03
These are non-optional integers. The compiler assured us that we were returning a valid, unwrapped type by making us consider all the possible cases, and all possible cases returned a value.
NULL 45:14
And this function cannot crash! The compiler practically walked us through implementing it and proved to us that it will produce an honest A value for the head, no optionals in sight, and it will definitely not crash.
NULL 45:32
This is why we continue to dive deeper and deeper into algebraic data types. They give us a bulletproof way of dissecting and evolving our types so that they can express as much of the problem domain as possible. In the case of non-empty, it allowed us to completely remove a potential crash from our type and now we can be confident in its use.
NULL 46:01
Even cooler, we can use this definition of NonEmptyList as a starting off point to build a far more powerful and far more ergonomic type in Swift that could be used in a real life, production code base today . However, we’re going to have to save that for a future episode, as well as a few more things that are left to discuss in algebraic data types! Downloads Sample code 0019-algebraic-data-types-pt3 Point-Free A hub for advanced Swift programming. Brought to you by Brandon Williams and Stephen Celis . Content Become a member The Point-Free Way Beta previews Gifts Videos Collections Free clips Blog More About Us Community Slack Mastodon Twitter BlueSky GitHub Contact Us Privacy Policy © 2026 Point-Free, Inc. All rights are reserved for the videos and transcripts on this site. All other content is licensed under CC BY-NC-SA 4.0 , and the underlying source code to run this site is licensed under the MIT License .