Video #297: Back to Basics: Equatable
Episode: Video #297 Date: Oct 7, 2024 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep297-back-to-basics-equatable

Description
In this series we go back to basics with a deep dive into the subject of Equatable types. Equatability is a deceptively simple topic. It is a surprisingly tricky protocol that has some very specific semantics that must be upheld baked into it, and there are many misconceptions on how one can or should conform types to this protocol.
Video
Cloudflare Stream video ID: 56a77e44ecf3bfce3b12f6c18fad4f5c Local file: video_297_back-to-basics-equatable.mp4 *(download with --video 297)*
References
- Discussions
- unlocked powerful existential features
- equivalence relation
- en.wikipedia.org/wiki/Equivalence_relation
- Equatable
- 0297-back-to-basics-equatable-pt1
- Brandon Williams
- Stephen Celis
- Mastodon
- GitHub
- CC BY-NC-SA 4.0
- source code
- MIT License
Transcript
— 0:05
We’re starting a new style of episode here on Point-Free called “Back to basics.”
— 0:10
Most of the time on Point-Free we enjoy diving deep into a topic by devoting multiple episodes to setting up the problem, then solving the problem in a robust way, and then showing how all new fantastic benefits start to pop out when you put in the hard work. And along the way we have explored many advanced topics of the Swift programming language, such as concurrency, value type semantics, existentials, advanced generics, and a lot more.
— 0:31
But often those lessons are tarnished by the fact that they are embedded in a long series of episodes on some other topic, and so many of our viewers may miss out who don’t meticulously watch every single episode. Brandon
— 0:43
And so our “Back to basics” episodes are going to focus on one very specific topic related to the Swift programming language, and try uncover everything there is to be known about that topic. And we are kicking things off with a seemingly simple topic, the Equatable and Hashable protocols. But it turns out this topic is not so simple after all. They are surprisingly tricky protocols and they have some very specific semantics baked into them that must be upheld, and unfortunately there are a lot of misconceptions in the Swift community on how one can conform types to this protocol. Especially when it comes to reference types. Stephen
— 1:20
So, let’s take a moment to familiarize ourselves with the Equatable protocol, give a deep reading of the documentation for it, and see how things can go wrong if you play fast and loose with the semantics of the protocol.
— 1:31
Let’s dig in. Equivalence relations
— 1:34
Let’s start with the basics of the Equatable protocol. It’s a pretty simple protocol with just one single requirement, a static == function that takes two values of the same type and returns a boolean: public protocol Equatable { static func == (lhs: Self, rhs: Self) -> Bool }
— 1:45
This protocol may seem innocent enough, but the fact that it uses Self in its definition makes this protocol different from most protocols one usually comes across. The Self refers to the conforming type, and using Self for the lhs and rhs arguments forces both arguments to be of the same type.
— 2:01
Protocols that involve Self or associated types used to be a real pain to deal with in Swift. Something as simple as this would not compile: let x: any Equatable
— 2:12
…because Swift could not allow such an expression without knowing what the Self type was.
— 2:17
But modern Swift, in particular Swift 5.7 and later, unlocked powerful existential features for all protocols, letting us finally do this: Unlock existentials for all protocols github.com/swiftlang/swift-evolution let x: any Equatable
— 2:26
And then if we ever need to “open” this existential to get access to the actual concrete Self type, there are tools for doing that, but that isn’t the focus of this episode so we don’t need to show that now.
— 2:35
Now suppose that we have a simple data type, like a struct, that holds onto only other simple data types: struct User { let id: Int var isAdmin = false var name: String }
— 2:43
Then we can apply the Equatable protocol and everything immediately compiles with no additional work: struct User: Equatable { … }
— 2:50
This is a special feature of Equatable imparted by the Swift compiler. Typically when you conform a type to a protocol that has requirements you get a compiler error letting you know that you must actually implement those requirements.
— 3:02
But in the case of Equatable , Swift sees that each field held in the struct is Equatable , and so decides to implement the == function in the most obvious way, by performing a field-wise comparison: static func == (lhs: Self, rhs: Self) -> Bool { lhs.id == rhs.id && lhs.isAdmin == rhs.isAdmin && lhs.name == rhs.name }
— 3:28
This is code that the Swift compiler is writing for us because 99.99% of the time it is the most correct form of the function. And this implementation of the == function is known as “structural equality”. It’s when you compare every single field in the struct for equality to make up the greater equality check of the values.
— 3:45
The docs also describe this behavior, and so it is well-understood: Note You can rely on automatic synthesis of the Equatable protocol’s requirements for a custom type when you declare Equatable conformance in the type’s original declaration and your type meets these criteria: For a struct, all its stored properties must conform to Equatable. For an enum, all its associated values must conform to Equatable. (An enum without associated values has Equatable conformance even without the declaration.)
— 4:08
Now the question is: why is this implementation of == the most correct one? Could we have also defined == in terms of just the ID: static func == (lhs: Self, rhs: Self) -> Bool { lhs.id == rhs.id }
— 4:18
After all, the ID is presumably unique for each user, so why even compare anything else? If the IDs are equal then certainly it’s the same user right?
— 4:26
But also couldn’t we have compared all the data of the user except their ID: static func == (lhs: Self, rhs: Self) -> Bool { lhs.isAdmin == rhs.isAdmin && lhs.name == rhs.name } This may be handy for when we want to compare to users based on just their outward facing qualities, rather than some cryptic ID that was assigned to them when the user record was created.
— 4:36
So that’s 3 forms of equality, which is correct?
— 4:40
Well, unfortunately the answer is murky. They are all technically correct, but it highly depends on the context they are being used. Each of these implementations may be correct or completely wrong depending on how we are using it.
— 4:50
The fact is that the Equatable protocol has no opinion on what kind of data you decide to compare in order to check for equality. All of these are equally valid in the eyes of the protocol.
— 5:00
There is, however, something that the protocol does deeply care about, and that is your implementation of == needs to follow some basic rules in order to be considered a “legitimate” conformance.
— 5:09
This is spelled out in the docs: Note Since equality between instances of Equatable types is an equivalence relation, any of your custom types that conform to Equatable must satisfy three conditions, for any values a , b , and c : a == a is always true (Reflexivity) a == b implies b == a (Symmetry) a == b and b == c implies a == c (Transitivity)
— 5:40
This is probably one of the math-iest passages in all of the Swift docs. An “ equivalence relation ” is a pure mathematical concept that was given an official name over a hundred years ago, but whose informal usage dates back to antiquity and Euclid.
— 5:54
We can even visit the Wikipedia page for “equivalence relation” to see its formal mathematical definition: Equivalence relation en.wikipedia.org
— 6:01
…and we will see that this definition is basically identical to what is in the Swift docs.
— 6:03
Each condition may seem obvious. Of course we expect a value to be equal to itself! But these 3 properties capture the essence of what it means for things to be equal, and it allows one to partition a set into disjoint subsets of equivalent elements.
— 6:17
And all of the == functions we defined for User : static func == (lhs: Self, rhs: Self) -> Bool { lhs.id == rhs.id && lhs.isAdmin == rhs.isAdmin && lhs.name == rhs.name } static func == (lhs: Self, rhs: Self) -> Bool { lhs.id == rhs.id } static func == (lhs: Self, rhs: Self) -> Bool { lhs.isAdmin == rhs.isAdmin && lhs.name == rhs.name }
— 6:21
…satisfy these 3 conditions. And the more strict the == check, the finer the partition is, and the more relaxed the == check, the coarser the partition is.
— 6:28
For example, when using structural equality for User , where we compare every single field: static func == (lhs: Self, rhs: Self) -> Bool { lhs.id == rhs.id && lhs.isAdmin == rhs.isAdmin && lhs.name == rhs.name }
— 6:33
…the set of all User values is partitioned into subsets of a single user. That means, given a user: let blob = User(id: 42, isAdmin: false, name: "Blob")
— 6:39
…there is only one single value in the entire User type equal to blob , and that is blob itself.
— 6:45
However, when checking only the user’s ID: static func == (lhs: Self, rhs: Self) -> Bool { lhs.id == rhs.id }
— 6:47
…when get much larger partitioned subsets. For example, if we have a bunch of users with the same ID but different name and isAdmin : let id42Partition = [ User(id: 42, isAdmin: true, name: "Blob"), User(id: 42, isAdmin: false, name: "Blob, Esq."), User(id: 42, isAdmin: true, name: "Blob, Esq."), User(id: 42, isAdmin: true, name: "Blob, MD"), User(id: 42, isAdmin: true, name: "Blob, MD"), … ]
— 6:57
…we will find that they are all part of the same partition group: id42Partition.allSatisfy { lhs in id42Partition.allSatisfy { rhs in lhs == rhs } } // true So there is basically one partition for each ID, and it holds a whole bunch of users with different names and different admin statuses.
— 7:23
There are also more exotic forms of equality that are interesting to study. For example, what if we had a type that wrapped an integer: struct Mod12: Equatable { var value: Int init(_ value: Int) { self.value = value } }
— 7:51
…but we consider two of these values equal only if the distance between them is a multiple of 12: static func == (lhs: Self, rhs: Self) -> Bool { (rhs.value - lhs.value).isMultiple(of: 12) }
— 8:05
In this world 1 and 6 are not equal, just like what we are used to: Mod12(1) == Mod12(6) // false
— 8:14
…but 1 and 13 are equal because they differ by 12: Mod12(1) == Mod12(13) // true
— 8:22
And 1 and 25 are equal because they differ by 10: Mod12(1) == Mod12(11) // true
— 8:28
This kind of arithmetic is sometimes called “clock arithmetic” because a clock has the numbers from 1 to 12, and when you add up numbers that go past 12 it wraps back around to 0. And that is exactly what is happening here. In Mod12 , the number 1 is equal to 13 and 25 and 37, and on and on.
— 8:46
It may seem strange, but this definition of == satisfies the properties of an equivalence relation just fine. And so this is a legitimate conformance of Equatable .
— 8:56
But there are ways of defining == that fall afoul of the equivalence relation properties. For example, what if we had a type that just always returned false for == : struct NeverEqual<A>: Equatable { var value: A init(_ value: A) { self.value = value } static func == (lhs: Self, rhs: Self) -> Bool { false } }
— 9:19
This equality function is symmetric and transitive, but it is not reflexive: let neverEqual1 = NeverEqual(1) neverEqual1 == neverEqual1 // false
— 9:33
That makes it possible to do non-sensical things, such as constructing an array holding a specific value, yet when we try to use the contains function to see what it is inside it reports back that it does not contain the value: let values = [NeverEqual(true), NeverEqual(false)] values.count // 2 values.contains(NeverEqual(true)) // false values.contains(NeverEqual(false)) // false
— 10:16
So, having a form of == that isn’t reflexive is not going to be very useful, and is going to lead us to write perfectly reasonable code that has very unreasonable results at runtime.
— 10:25
There are also ways of defining == that aren’t symmetric. For example, what if we defined integers being “equal” if one is less than or equal to the other: struct LessThanOrEqual { var value: Int init(_ value: Int) { self.value = value } static func == (lhs: Self, rhs: Self) -> Bool { lhs.value <= rhs.value } }
— 10:48
This relation is now reflexive and transitive, but it is not symmetric: LessThanOrEqual(2) == LessThanOrEqual(3) // true LessThanOrEqual(3) == LessThanOrEqual(2) // false
— 11:08
This is problematic because it means that a seemingly innocuous change to an algorithm can cause completely different results. For example, consider the algorithm that returns the first position of an element in an array of equatable values: extension Array where Element: Equatable { func firstOffset(of needle: Element) -> Int? { for (offset, element) in enumerated() { if needle == element { return offset } } return nil } }
— 11:31
Of course the standard library already comes with a firstIndex(of:) method, but let’s forget that for a moment.
— 11:35
In this implementation we decided to search for the needle in the elements like this: if needle == element {
— 11:39
But this could have easily been this: if element == needle {
— 11:44
And the choice of ordering this equality check is not something we should really have to think about. We should be able to choose one and expect it to behave in the most obvious way since they should be equivalent.
— 11:53
However, this choice turns out to have quite an impact when your equality implementation is not symmetric. For example, this: [ LessThanOrEqual(2), LessThanOrEqual(3), ] .firstOffset(of: LessThanOrEqual(3)) // 0
— 12:18
…produces 0 as the offset.
— 12:21
But if we switch the order of the comparison: if needle == element {
— 12:24
…then the expression suddenly evaluates to 1: [ LessThanOrEqual(2), LessThanOrEqual(3), ] .firstOffset(of: LessThanOrEqual(3)) // 1
— 12:26
We would never want to have to worry about the order we apply == , and so this is why it is important to uphold this property of the equivalence relation.
— 12:34
And finally, there are ways to define equality that are reflexive and symmetric, but not transitive, and this also causes problems. Take for example a wrapper type around Double that says two doubles are equal if they are “close enough”: struct Approximation: Equatable { var value: Double init(_ value: Double) { self.value = value } static func == (lhs: Self, rhs: Self) -> Bool { abs(lhs.value - rhs.value) < 0.1 } }
— 13:12
This is not transitive because the small inaccuracies between values can accumulate as you compare more and more values: Approximation(1) == Approximation(1.05) // true Approximation(1.05) == Approximation(1.1) // true Approximation(1) == Approximation(1.1) // false
— 13:42
Here we have a situation where “a” is equal to “b”, and “b” is equal to “c”, yet somehow “a” is not equal to “c”. So that’s not great, but also this data type and Equatable conformance seems like it may be a handy and reasonable thing to do, but unfortunately it wreaks havoc on our intuition.
— 13:58
For example, suppose we have a seemingly innocent function that filters out duplicates from an array of equatables: extension Array where Element: Equatable { func uniques() -> > Array { var result = Array() for element in self { if !result.contains(element) { result.append(element) } } return result } }
— 14:06
Of course this is something that sets are really good at doing too, but we may want to preserve order and we may prefer to have the weaker Equatable constraint over the stronger Hashable constraint that Set requires.
— 14:15
Next consider an array of these approximate doubles that are very close to each other, and then take their uniques: [ Approximation(1.05), Approximation(1), Approximation(1.025), Approximation(1.075), Approximation(1.1), Approximation(1.15), ] .uniques() .map(\.value) // [1.05]
— 14:26
This squashed the entire array down into one element, because the first value, 1.05, is indeed within 0.1 of all the other values.
— 14:35
However, if we just slightly change the order of the array we will find a different result: [ Approximation(1), Approximation(1.05), Approximation(1.025), Approximation(1.075), Approximation(1.1), Approximation(1.15), ] .uniques() .map(\.value) // [1.0, 1.1]
— 14:42
Now two elements are in the “uniques” because the first element considered, 1, is more than 0.1 distance from the last element. Substitutability
— 14:50
All of these examples are showing how important it is to adhere to the properties of equivalence relations when defining == on your types. Even just slightly fudging one single property can cause reasonable looking code to produce very unreasonable output, and that will leak complexity and unknowability into your code base.
— 15:07
And so whether you like math or not, there are mathematical laws to follow when implementing Equatable , or else chaos will ensue. But rather than being annoyed that a seemingly abstract math concept has crept into our very pragmatic programming language, we should be happy!
— 15:21
The notion of an equivalence relation was used informally since antiquity, and then was formally defined over one hundred years ago, and so if it has served mathematics for that long then I think it gives us a lot of confidence that it will serve us well too. I would be surprised if there is any single piece of software out there that will stand the test of time as the humble equivalence relation has. Brandon
— 15:41
So it seems like we now understand equality in Swift. There is this notion of an “equivalence relation” that is defined by 3 properties: reflexivity, symmetry and transitivity. If our Equatable conformances adhere to these properties then we will have a well-behaved notion of equality that allows us to reason about our code.
— 16:01
However, there is another paragraph in the Equatable protocol docs that discusses something that is secretly mathematical, but unfortunately does so in a very un-mathematical way. Some members of the core Swift team have even expressed their wish to remove the paragraph from the docs. We disagree with that sentiment, and instead we think the docs should be rewritten for greater clarity and even expand quite a bit on the topic since we feel it is very important to keep in mind when writing code.
— 16:30
So let’s take a look.
— 16:33
If we look at the docs for Equatable one more time, we will find the following paragraph preceding the description of equivalence relations: Note Equality implies substitutability—any two instances that compare equally can be used interchangeably in any code that depends on their values. To maintain substitutability, the == operator should take into account all visible aspects of an Equatable type. Exposing nonvalue aspects of Equatable types other than class identity is discouraged, and any that are exposed should be explicitly pointed out in documentation.
— 17:09
This is quite a bit harder to grok than the description of equivalence relations. It uses more ambiguous and undefined terms, such as “used interchangeably”, “visible aspects” and “nonvalue aspects”.
— 17:27
But overall what this is trying to convey is the following. Suppose you had a function that took an Equatable value as an input. And for simplicity we will have this function just return a boolean: func f<A: Equatable>(_ a: A) -> Bool { … }
— 17:50
You would probably want the property that equal values transform into equal values: let x: A let y: A if x == y { assert(f(x) == f(y)) }
— 18:19
If you didn’t have this property then you could again write seemingly reasonable code that gives completely unreasonable results. For example, what if during the process of implementing an algorithm you had two values, and if the values are equal you can execute a fast path by using just one of the values: func algorithm<A: Equatable>(lhs: A, rhs: A) -> Bool { if lhs == rhs { // Fast path using just 'lhs' } else { // Slow path using both 'lhs' and 'rhs' } }
— 19:10
It would be pretty disastrous if the output of this algorithm depended on whether we used the lhs value or rhs value in the fast path. We should of course be free to use lhs and rhs interchangeably since they are equal, but this “substitutability” principle in the docs is warning that it’s not always a given.
— 19:37
This is an incredibly important concept, but what is strange about it being in the docs of Equatable is that it is a supplementary concept to equatability. It is not an intrinsic property of Equatable types.
— 19:49
In fact, the phrasing at the beginning of the paragraph: Note Equality implies substitutability…
— 19:55
…is just flat out wrong. A type being Equatable does not in any way guarantee some kind of “substitutability” property. It is just a completely separate concept living right alongside equatability.
— 20:08
To convince yourself of this note that in order to define the “substitutability” concept they had to introduce additional concepts outside of the type conforming to Equatable . In particular, given a function f that takes an Equatable input, the substitutability property is upheld if equal values transform into equal values: let x: A let y: A if x == y { assert(f(x) == f(y)) }
— 20:30
So it’s not even possible to define this concept abstract in terms of a single type conforming to the Equatable protocol and nothing else. It is in fact not a property of the type at all, but rather a property of the function . This is why this paragraph in the docs should be clarified.
— 20:45
To really drive home this point, we are going to show that it is possible to have a perfectly legitimate Equatable conformance on a type that seemingly does not satisfy substitutability.
— 20:55
Remember that Mod12 type from earlier that expressed equality of integers if their difference is a multiple of 12: struct Mod12: Equatable { var value: Int init(_ value: Int) { self.value = value } static func == (lhs: Self, rhs: Self) -> Bool { (rhs.value - lhs.value).isMultiple(of: 12) } }
— 21:05
This is a perfectly fine implementation of the Equatable protocol. It satisfies all of the axioms of an equivalence relation, which means its reflexive, symmetric, and transitive.
— 21:15
And we can define functions on this type that satisfies the substitutability principle. For example, an add method: struct Mod12: Equatable { … func add(to other: Self) -> Self { Self(value + other.value) } }
— 21:31
This is satisfies the substitutability principle because if two Mod12 values are equal, then their sum is also going to be equal: let three = Mod12(3) let fifteen = Mod12(15) Mod12(2).add(to: three) == Mod12(2).add(to: fifteen) // true
— 22:10
This is just one example, but this holds for all Mod12 values. So this add(to:) method does satisfy the “substitutability” property, in that if two values are equal as Mod12 values then we can swap them out as arguments to the add(to:) method.
— 22:35
However, even though Mod12 is a perfectly fine equatable type and clearly does have some functions that satisfy substitutability, it is still very easy to define a function on Mod12 that violates the principle.
— 22:50
For example, consider this seemingly innocent function as a computed property: extension Mod12 { var isBig: Bool { value >= 100 } }
— 23:04
This is actually quite a dangerous thing to define if you want it to be well-behaved in real usage.
— 23:08
For example, suppose we take two “small” Mod12 values that are equal and feed them to the isBig function: Mod12(2) == Mod12(50) // true isBig(Mod12(2)) == isBig(Mod12(50)) // true
— 23:33
Since 2 and 50 are equal in the Mod12 type we expect their isBig outputs to also be equal, and indeed they are.
— 23:42
However, if we increase the value: Mod12(2) == Mod12(110) // true isBig(Mod12(2)) == isBig(Mod12(110)) // false We now have two Mod12 values that are equal, yet one is “big” and the other is not.
— 24:04
This seems to violate the substitutability principle, so does that mean the Equatable conformance on Mod12 is incorrect? Well no, as we said, it satisfies the axioms of an equivalence relation, and so it is perfectly legitimate.
— 24:21
What is illegitimate is defining a function on Mod12 that doesn’t respect the underly Equatable conformance on the type. The function should output the same value when fed equal values. Such a function can wreak havoc on our ability to locally reason about how our code behaves when dealing with Mod12 values. For example, suppose you have an algorithm that takes two Mod12 values and the first step you perform in the algorithm is to compute isBig on both of them: func algorithm(lhs: Mod12, rhs: Mod12) { let bothAreBig = lhs.isBig && rhs.isBig … }
— 25:10
But suppose we know that the isBig function is computationally expensive, and so we would like to reduce the number of times it is called. Well, then it would be very reasonable to do something like the following: func algorithm(lhs: Mod12, rhs: Mod12) { let bothAreBig = if lhs == rhs { lhs.isBig } else { lhs.isBig && rhs.isBig } … }
— 25:33
However, this is not valid to do since isBig can produce different outputs when given different yet equal values of Mod12 .
— 25:50
And so it’s the function that violates the substitutability principle, not the type, and that is why substitutability has nothing to do with the type, and everything to do with functions.
— 26:03
And this principle is actually mathematical, though it is not called substitutability. It is called “well-definedness” of functions, and it is described clearly on the Wikipedia page for equivalence relations: Note If ∼ is an equivalence relation on X, and P(x) is a property of elements of X, such that whenever x∼y, P(x) is true if P(y) is true, then the property P is said to be well-defined or a class invariant under the relation ∼. en.wikipedia.org/wiki/Equivalence_relation
— 26:43
This is basically how we have interpreted the substitutability principle from the Swift docs. It says that a property P of X is “well-defined” if P(x) equals P(y) whenever x equals y .
— 26:57
So, when the Swift docs discuss the concept of “substitutability” as it pertains to Equatable types, what they are really discussing is “well-definedness” of functions defined on Equatable types.
— 27:10
When defining functions on Equatable types it is important to consider the implementation of the == function to make sure that your function is well-defined. This means that if == only takes into account some subset of fields, you will need to be careful to not take into account any of that data when defining functions on the type. Or, at the very least, understand that substitutability is not guaranteed, and so feeding different but equal values to your functions may produce different results.
— 27:41
There is one situation in which well-definedness is guaranteed, and that is when equality is defined in terms of structural equality. Remember this is the type of equality where you check every single field of a struct for equality. You don’t leave any data out whatsoever.
— 28:00
When this form of equality is defined, then well-definedness of functions is guaranteed because it’s not possible to have multiple values with different data that are secretly equal. So, this problem of well-defined functions or substitutability is only worth worrying about when defining custom conformances to the Equatable protocol. Next time: Hashable
— 28:24
So, that was a really deep dive into the Equatable protocol. I bet most of our viewers didn’t expect to get dragged into a abstract mathematical hole in order to understand something that seems to be so simple.
— 28:35
And here is where we try to bring things down to Earth a bit.
— 28:39
Everything we have said up to this point is technically correct, and as you can see there are a lot of weird things that can happen if you start to indiscriminately fudge with the concept of equality. You can very easily write reasonably looking code that gives very unreasonable results, and that completely breaks out intuition when it comes to understanding how code is supposed to work by reading it. Stephen
— 28:59
However, even in the standard library there are multiple examples of Equatable implementations that do not live up to the promise of the documentation of the protocol. And sometimes we need to strike a balance between an idealized mathematical world, and the real, pragmatic world.
— 29:13
So, let’s take a few looks at how these ideas can fall apart in practice, and why we have chosen to live with it…next time! References Equatable Note A type that can be compared for value equality. Documentation for the Swift protocol. https://developer.apple.com/documentation/swift/equatable Equivalence relation Note In mathematics, an equivalence relation is a binary relation that is reflexive, symmetric and transitive. The Wikipedia page defining an “equivalence relation,” a mathematical concept underpinning Swift’s Equatable protocol. https://en.wikipedia.org/wiki/Equivalence_relation SE-0309: Unlock existentials for all protocols Anthony Latsis, Filip Sakel, Suyash Srijan • Sep 26, 2020 Note This proposal is a preamble to a series of changes aimed at generalizing value-level abstraction (existentials) and improving its interaction with type-level abstraction (generics). https://github.com/swiftlang/swift-evolution/blob/main/proposals/0309-unlock-existential-types-for-all-protocols.md Downloads Sample code 0297-back-to-basics-equatable-pt1 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 .