EP 35 · Advanced Protocol Witnesses · Oct 29, 2018 ·Members

Video #35: Advanced Protocol Witnesses: Part 1

smart_display

Loading stream…

Video #35: Advanced Protocol Witnesses: Part 1

Episode: Video #35 Date: Oct 29, 2018 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep35-advanced-protocol-witnesses-part-1

Episode thumbnail

Description

Now that we know it’s possible to replace protocols with concrete datatypes, and now that we’ve seen how that opens up new ways to compose things that were previously hidden from us, let’s go a little deeper. We will show how to improve the ergonomics of writing Swift in this way, and show what Swift’s powerful conditional conformance feature is represented by just plain functions.

Video

Cloudflare Stream video ID: e6a1e55b96daf3a2c93528e97213c480 Local file: video_35_advanced-protocol-witnesses-part-1.mp4 *(download with --video 35)*

References

Transcript

0:05

In the last two weeks (episodes #33 and #34 ) we explored the idea that protocols could largely be translated into concrete datatypes, and in doing so we fixed some of the problems that protocols have, but more importantly, we opened up the doors to some amazing compositions that were completely hidden from us with protocols. Towards the end of those two episodes we claimed that knowing this correspondence between protocols and concrete datatypes could actually lead us to better API and library design, but stopped short on the details.

0:51

Well, we want to describe that, but before we can get there we need to go a little deeper with this correspondence. Currently our dictionary to translate between the world of protocols and the world of concrete types is woefully incomplete. There are tons of protocol concepts that we haven’t yet described how to translate over to concrete types, and knowing that information will provide us the tools we need to tackle API and library design. So, that is this week’s goal!

1:18

But first, we want to introduce a lil clean up terminology that we’ve been using on the series… Renaming contramap

1:25

A few months ago we introduced the idea of contravariance , and showed that it’s a very natural idea hidden in a very counterintuitive package. It’s like the map we know and love on arrays and optionals, but it goes in the opposite direction. We applied it to the idea of predicate sets, and showed that it helps us see a form of composition that we may not have looked for otherwise.

1:55

Then, last week , in a very unexpected way we showed that contramap appeared when discussing protocols and witnesses to those protocols. That was very surprising, and was very powerful because it allowed us to derive all new witnesses from existing witnesses.

2:21

However, the name contramap isn’t fantastic. We used contramap because other communities use this term, like Haskell and PureScript and Scala, though some of these communities shorten it to comap and cmap . In one way it’s nice because it is indeed like the contravariant version of map . It has basically the same shape as map, it’s just that the arrow flips the other direction. Even so, the term may seem a little overly-jargony and may turn people off to the idea entirely, and that would be a real shame.

2:42

Luckily, there’s a concept in math that is far more general than the idea of contravariance, and in the case of functions is precisely contramap , and even better it has a great name.

3:13

In our episode on contramap , we defined a Predicate type that is merely a wrapper around a function (A) -> Bool . struct Predicate<A> { let contains: (A) -> Bool func contramap<B>(_ f: @escaping (B) -> A) -> Predicate<B> { return Predicate<B> { self.contains(f($0)) } } } It’s equipped with a contramap operation that says: “If you tell me how to transform B s into A s, I’ll tell you how to transform Predicate<A> s into Predicate<B> s.” The relationship is strangely flipped.

3:39

With this type we started defining predicates and showed how you can ask them if they contain values: let isLessThan10 = Predicate { $0 < 10 } isLessThan10.contains(5) // true isLessThan10.contains(11) // false

3:59

With predicates defined, we showed that you can use contramap to derive all new predicates out of existing ones. For example, we can call contramap on isLessThan10 to build a predicate on short strings by using the string’s count . let shortStrings = isLessThan10.contramap { (s: String) in s.count } shortStrings.contains("Blob") // true shortStrings.contains("Blobby McBlob") // false

5:02

Things got really powerful when we had our Overture library at our disposal with all of its functional helpers. It allowed us to use contramap in a really short fashion using key-path getters. import Overture let shortStrings = isLessThan10.contramap(get(\String.count)) shortStrings.contains("Blob") shortStrings.contains("Blobby McBlob")

5:45

And that’s how Predicate and contramap works, and how contramap works in general, but as we said, it’s a bit of a jargony term. So the term we’re going to use instead, which comes from math, is called the “pullback.” Intuitively it expresses the idea of pulling a structure back along a function to another structure. And this is kind of what these predicates are doing: we have a predicate on integers, isLessThan10 , and we want to pull it back to work on strings, a much larger structure of which you can project into the integers by taking their character count. Let’s see how that reads.

6:26

We can copy and paste contramap and rename it pullback . No other changes are needed. func pullback<B>(_ f: @escaping (B) -> A) -> Predicate<B> { return Predicate<B> { self.contains(f($0)) } }

6:33

But now we can say things like: “Take the ‘is less than ten’ predicate on integers, and pull it back to strings by getting the character count.” isLessThan10.pullback(get(\String.count)) That reads really nicely!

6:48

Let’s also apply to the contramap we came across in the last episode . We can paste in a bunch of work from last time: struct Describing<A> { var describe: (A) -> String func contramap<B>(_ f: @escaping (B) -> A) -> Describing <B> { return Describing<B> { b in self.describe(f(b)) } } } struct PostgresConnInfo { var database: String var hostname: String var password: String var port: Int var user: String } let compactWitness = Describing<PostgresConnInfo> { """ PostgresConnInfo(\ database: "\($0.database)", \ hostname: "\($0.hostname)", \ password: "\($0.password)", \ port: "\($0.port)", \ user: "\($0.user)"\ ) """ } let prettyWitness = Describing<PostgressConnInfo> { """ PostgresConnInfo( database: "\($0.database)", hostname: "\($0.hostname)", password: "\($0.password)", port: "\($0.port)", user: "\($0.user)" ) """ } let secureCompactWitness = compactWitness.contramap( set(\.password, "") ) let securePrettyWitness = prettyWitness.contramap( set(\.password, "") ) Here we have our Describing type and its version of contramap , along with a type PostgresConnInfo , which has a couple ways of being described using Describing witnesses. From both of these witnesses we were able to derive new witnesses that redacted the password information.

7:52

Let’s define pullback on Describing . It’s the same implementation as contramap . func pullback<B>(_ f: @escaping (B) -> A) -> Describing <B> { return Describing<B> { b in self.describe(f(b)) } }

8:02

And then let’s use the friendlier pullback method to describe our transformations. compactWitness.pullback(set(\.password, "**")) Take the compact witness, and pull it back to another witness on PostgresConnInfo where we’ve redacted the password.

8:20

This reads really nicely now! Whether we’re dealing with predicates or the Describing type, pullback reads really nicely. It lets us take a value on some type and pull it back onto some bigger value that maps into that first value.

8:37

Although it’s unfortunate to rename such a fundamental concept after having learned it many months ago, we think it’s worth it. It’s friendlier to newcomers t trying to understand this topic, and it’s friendlier in general. Swift Evolution has proposed introducing a predicate set type into the standard library, and while contramap seems like an unlikely addition, pullback just might make it. This name reads well and has a lot of great intuition, and we’re not just making it up! It comes from math and has been around for many decades.

9:27

We still think the contramap name is still important, since it’s more familiar in the computer science world, and because the contra - prefix allows us to transform any concept into its contravariant dual concept, and it will be creeping into some future episodes, but from now we will be mostly using pullback.

9:41

So, with that out of the way, back to our regularly scheduled programming… Witness ergonomics

9:52

While we’re making improvements, and before we get into more advanced witness topics, let’s start simple by discussing a slight improvement to the ergonomics of our witness values. Currently we have our witness values just floating around in a file, not really tied to anything, and I think that may make some people feel uneasy.

10:18

Last time we had these concrete datatypes and these witnesses: struct Combining<A> { let combine: (A, A) -> A } struct EmptyInitializing<A> { let create: () -> A } let sum = Combining<Int>(combine: +) let zero = EmptyInitializing { 0 } let product = Combining<Int>(combine: *) let one = EmptyInitializing { 1 }

11:13

And then we wrote a generic algorithm with these types in order to reduce a collection of values into a single value: extension Array { func reduce( _ initial: EmptyInitializing<Element>, _ combining: Combining<Element> ) -> Element { return self.reduce(initial.create(), combining.combine) } } [1, 2, 3, 4].reduce(zero, sum) // 10 [1, 2, 3, 4].reduce(one, product) // 24

12:09

Now it can seem a bit uncomfortable, creating and passing these witnesses in the global namespace, but there’s something really easy we can do to anchor these witnesses to a namespace that may seem more familiar and comfortable to our viewers. Let’s just move them to be statics inside each concrete data type. extension Combining where A == Int { static let sum = Combining(combine: +) static let product = Combining(combine: *) } extension EmptyInitializing where A == Int { static let zero = EmptyInitializing { 0 } static let one = EmptyInitializing { 1 } }

13:10

Now that our witnesses are anchored to their concrete types, we can use them: [1, 2, 3, 4].reduce(EmptyInitializing.zero, Combining.sum) // 10 [1, 2, 3, 4].reduce(EmptyInitializing.one, Combining.product) // 24

13:41

We can use our generic algorithm in much the same way, but we’re no longer relying on globals. We have values that are nicely nested in a type.

13:49

But because we defined these witnesses as statics on the same type, we can leverage type inference and make something verbose into something quite succinct: [1, 2, 3, 4].reduce(.zero, .sum) // 10 [1, 2, 3, 4].reduce(.one, .product) // 24 That’s not bad! We’ve regained the brevity of the original expression by utilizing a first-class Swift feature, and now our witnesses aren’t just floating around.

14:20

Even better, because we are nested in a generic struct we can leverage that type parameter to make an even more generic version of these witnesses. Instead of constraining to A == Int we can do A: Numeric and everything should continue working as before, but now we can use it for any numeric value, not just integers: extension Combining where A: Numeric { static var sum: Combining { return Combining(combine: +) } static var product: Combining { return Combining(combine: *) } } extension EmptyInitializing where A: Numeric { static var zero: EmptyInitializing { return EmptyInitializing { 0 } } static var one: EmptyInitializing { return EmptyInitializing { 1 } } } We couldn’t use let because we static stored properties aren’t supported in generic types. Previously, when we constrained A == Int we were no longer in a generic scope, so we were free to define static constants in a succinct manner. The fix was easy, though: we can use computed properties.

15:14

Everything compiled and still works, but now we can reduce on any Numeric type, like Double : [1.1, 2, 3, 4].reduce(.zero, .sum) // 10.1 [1.1, 2, 3, 4].reduce(.one, .product) // 26.4

15:27

This generalization is only possible because of our nesting in a generic struct. Swift doesn’t support generics for var s and let s but we can fake it here.

15:42

This organization principle doesn’t only work with the witnesses we have constructed above. The concrete type is the natural home for all witness values as static vars and functions. This will help collect and organize all of your witnesses, and make them easy to search via autocompletion.

16:02

What about Describing ? We have two different descriptions of PostgresConnInfo that we can nest in the Describing type as statics. extension Describing where A == PostgresConnInfo { static let compactWitness = Describing { """ PostgresConnInfo(\ database: "\($0.database)", \ hostname: "\($0.hostname)", \ password: "\($0.password)", \ port: "\($0.port)", \ user: "\($0.user)"\ ) """ } static let prettyWitness = Describing { """ PostgresConnInfo( database: "\($0.database)", hostname: "\($0.hostname)", password: "\($0.password)", port: "\($0.port)", user: "\($0.user)" ) """ } } We can just copy and paste our global witnesses into the Describing namespace, constrained to our type, and change the let to static let .

16:39

So let’s use them! Last time we had a localhostPostgres value and a generic algorithm for printing any Describing witness: let localhostPostgres = PostgresConnInfo( database: "pointfreeco_development", hostname: "localhost", password: "", port: 5432, user: "pointfreeco" ) func print<A>(tag: String, _ value: A, _ witness: Describing<A>) { print("[\(tag)] \(witness.describe(value))") }

16:50

Now that we have witnesses that aren’t floating in the module namespace. We can now pass them to our print function. print(tag: "debug", localhostPostgres, .compact)

17:30

The really cool part is that because we’ve constrained our extension to be A == PostgresConnInfo we don’t have any risk of these statics conflicting with other statics in Describing .

17:45

For instance, we could add some Describing witnesses on Bool with the same statics: extension Describing where A == Bool { static var compact: Describing { return Describing { $0 ? "t" : "f" } } static var pretty: Describing { return Describing { $0 ? "𝓣𝓻𝓾𝓮" : "𝓕𝓪𝓵𝓼𝓮" } } }

18:13

And we can use the same identifiers: print(tag: "debug", true, .compact) // [debug] t print(tag: "debug", true, .pretty) // [debug] 𝓣𝓻𝓾𝓮

18:28

Our ability to reuse the same compact identifier for any type is really cool! We practically have infinitely many unique namespaces parameterized by the type. This means we can make “pretty” and “compact” witnesses for other types:

18:42

In the previous two episodes, we introduced the concept of witnesses, but we did so in a very blasé fashion, where we defined a bunch of witnesses all willy-nilly in the global namespace, which doesn’t translate so nicely into real-world code bases. It’s very cool that with just a few language-level features we’re able to improve the ergonomics substantially. We end up with a friendly API that feels very “Swifty.” Conditional conformance

19:25

Now we’ve seen that the ergonomics of using concrete types and witnesses instead of protocols can be improved. Also last week we saw that these witnesses have a lot of interesting composability properties that are completely hidden from us when dealing with protocols.

19:33

So let’s move on to seeing what other amazing things concrete types and witnesses can do. One of the most requested of features for Swift back in the day was that of “conditional conformance.” It’s what allows you to express the idea of generic types conforming to a protocol if the type parameter also conforms to some protocol. The most classic example is that of arrays of equatable should be equatable, which was not possible to express until Swift 4.1, about 4 years after Swift was first announced.

20:03

So, it may be surprising to our viewers to learn that witnesses have no such problems, and in fact it was possible to do conditional conformances with witnesses since day one of Swift. All you need is structs, generic types, and functions!

20:21

This is what conditional conformance looks like in Swift extension Array: Equatable where Element: Equatable { … }

20:43

In order to show this off we first need to convert the Equatable protocol to a concrete datatype.

20:53

This is the way the Equatable protocol is defined: protocol Equatable { static func == (lhs: Self, rhs: Self) -> Bool }

21:11

We can translate this to a struct: struct Equating<A> { let equals: (A, A) -> Bool } The generic A is the same as the Self that we see in the protocol.

22:14

Now we can cook up a witness on Equating : extension Equating where A == Int { static let int = Equating { $0 == $1 } } Nothing fancy. We’re reusing the == function already defined for Int s.

22:50

We can even write this in a point-free fashion: extension Equating where A == Int { static let int = Equating(equals: ==) }

22:55

What does it mean to have “conditional conformance?” Let’s look at our earlier example. extension Array: Equatable where Element: Equatable { … } This says that if our Element is Equatable , we can induce or create an Equatable conformance for an Array of those elements.

23:15

This is kind of like a mapping. We take an Element: Equatable on the part and map it into an Array: Equatable on the whole.

23:31

This is exactly what conditional conformance is! We can cook up a plain ole function to do the same thing. Given an Equating<A> we should be able to return an Equating<[A]> . func array(_ equating: Equating<A>) -> Equating<[A]> { return Equating<[A]> { lhs, rhs in … } }

24:10

Not we may think we can just return lhs == rhs , but our `A is unconstrained. In the witness world, we no longer constrain our types to protocols. Instead we pass around explicit witnesses.

24:31

So let’s do the work ourselves: func array(_ equating: Equating<A>) -> Equating<[A]> { return Equating<[A]> { lhs, rhs in guard lhs.count == rhs.count else { return false } for (lhs, rhs) in zip(lhs, rhs) { if !equating.equals(lhs, rhs) { return false } } return true } }

25:50

We now have a function that allows us to transform an Equating<A> into an Equating<[A]> .

25:59

And we can take it for a spin: array(.int).equals([], []) // true array(.int).equals([1], [1]]) // true array(.int).equals([1], [1, 2]) // false

26:33

We’ve done it! We’ve recovered conditional conformance for the Equating witness. But it’s a free function, and for the same reason you may not want your witnesses to be floating around in the global namespace, you may not want your conditional conformances to be floating around in the global namespace, but luckily it makes sense to package this up in the concrete type as well: extension Equating static func array(of equating: Equating<A>) -> Equating<[A]> { return Equating<[A]> { lhs, rhs in guard lhs.count == rhs.count else { return false } for (a, b) in zip(lhs, rhs) { if !equating.equals(a, b) { return false } } return true } } } Equating.array(of: .int).equals([], []) // true Equating.array(of: .int).equals([1], [1]]) // true Equating.array(of: .int).equals([1], [1, 2]) // false And this is reading really nicely.

27:42

But since we’re dealing with witnesses we can cook up all new notions of equality without trampling over the first definition of equality. We just need a pullback . func pullback<B>(_ f: @escaping (B) -> A) -> Equating<B> { return Equating<B> { lhs, rhs in self.equals(f(lhs), f(rhs)) } }

28:44

And this is pullback on Equating ! It allows us to transform one Equating witness into another by pulling back over another type.

28:44

For example, we could pull back our integer equality to be string equality where to strings are equal if their lengths are equal: let stringCount = Equating.int.pullback(get(\String.count)) // Equating<String>

29:02

Now we have a whole new Equating witness on strings, but it only equates strings based on having the same character count. Equating.array(of: stringCount).equals([], []) // true Equating.array(of: stringCount).equals(["Blob"], ["Blob"]) // true Equating.array(of: stringCount).equals(["Blob"], ["Bolb"]) // true Equating.array(of: stringCount).equals(["Blob"], ["Blob Sr"]) // false

29:49

It’s a strange notion of equality, but it was so simple to define and transform! And remember, this would be completely impossible in the protocol world. There’s only one notion of Equatable per type. But here we get to introduce new notions of equality and use them however we want! And we can do so with our own form of conditional conformance. Nested conditional conformance

30:54

Another cool feature of conditional conformance is that it nests. For example: [[Int]] An array of array of Int s is Equatable because arrays of Int s are conditionally Equatable , and so arrays of those arrays of Int s are so too. [[1, 2], [3, 4]] == [[1, 2], [3, 4, 5]] // false [[1, 2], [3, 4]] == [[1, 2], [3, 4]] // true

31:25

What does this nesting look like with concrete types and witnesses? Amazingly, nested conditional conformances are expressed by function composition! This means that given an equating witness on some type, you can induce an equating witness on double nested arrays of that type by just composing this array function with itself: (Equating.array >>> Equating.array)(.int) // Equating<[[Int]]>

32:07

This transports the int witness of equality on integers all the way over to double nested arrays of integers.

32:29

We could then use this nested witness in the following way: (Equating.array >>> Equating.array)(.int) .equals([[1, 2], [3, 4]], [[1, 2], [3, 4]]) // true (Equating.array >>> Equating.array)(.int) .equals([[1, 2], [3, 4]], [[1, 2], [3, 4, 5]]) // false

32:54

We highly recommend sitting down with this composition and fully understanding all the pieces because it really packs a punch.

33:05

And of course we can also use our string equality with this nesting: (Equating.array >>> Equating.array)(stringCount) .equals([["Blob", "Blob Jr"]], [["Bolb", "Bolb Jr"]]) // true (Equating.array >>> Equating.array)(stringCount) .equals([["Blob", "Blob Jr"]], [["Blob", "Bolb Esq]]) // false

34:12

It’s amazing that we can recover conditional conformance with just plain functions. It seems like such a complex feature, and it definitely is from the perspective of protocols and type systems, but at the value level it is so simple. Till next time…

34:53

A complicated compiler feature can be reframed as function composition. Still, we have that compiler feature and we can use it, even if it took four years to get there. And we used it to great benefit with our Tagged and NonEmpty types. Still, there are plenty of things that you can’t do these days with protocols, but you can do them with witnesses. References Protocol-Oriented Programming in Swift Apple • Jun 16, 2015 Apple’s eponymous WWDC talk on protocol-oriented programming: At the heart of Swift’s design are two incredibly powerful ideas protocol-oriented programming and first class value semantics. Each of these concepts benefit predictability, performance, and productivity, but together they can change the way we think about programming. Find out how you can apply these ideas to improve the code you write. https://developer.apple.com/videos/play/wwdc2015/408/ Modern Swift API Design Apple • Jan 2, 2019 As of WWDC 2019, Apple no longer recommends that we “start with a protocol” when designing our APIs. A more balanced approach is discussed instead, including trying out concrete data types. Fast forward to 12:58 for the discussion. Note Every programming language has a set of conventions that people come to expect. Learn about the patterns that are common to Swift API design, with examples from new APIs like SwiftUI, Combine, and RealityKit. Whether you’re developing an app as part of a team, or you’re publishing a library for others to use, find out how to use new features of Swift to ensure clarity and correct use of your APIs. https://developer.apple.com/videos/play/wwdc2019/415/?time=778 Pullback We use the term pullback for the strange, unintuitive backwards composition that seems to show up often in programming. The term comes from a very precise concept in mathematics. Here is the Wikipedia entry: In mathematics, a pullback is either of two different, but related processes precomposition and fibre-product. Its “dual” is a pushforward. https://en.wikipedia.org/wiki/Pullback Protocols with Associated Types Alexis Gallagher • Dec 15, 2015 This talk by Alexis Gallagher shows why protocols with associated types are so complicated, and tries to understand why Swift chose to go with that design instead of other alternatives. https://www.youtube.com/watch?v=XWoNjiSPqI8 Protocol Oriented Programming is Not a Silver Bullet Chris Eidhof • Nov 24, 2016 An old article detailing many of the pitfalls of Swift protocols, and how often you can simplify your code by just using concrete datatypes and values. Chris walks the reader through an example of some networking API library code, and shows how abstracting the library with protocols does not give us any tangible benefits, but does increase the complexity of the code. http://chris.eidhof.nl/post/protocol-oriented-programming/ Value-Oriented Programming Matt Diephouse • Jul 29, 2018 Matt gives another account of protocol-oriented programming gone awry, this time by breaking down the famous WWDC talk where a shape library is designed using protocols. By rewriting the library without protocols Matt ends up with something that can be tested without mocks, can be inspected at runtime, and is more flexible in general. https://matt.diephouse.com/2018/08/value-oriented-programming/ Scrap your type classes Gabriella Gonzalez • May 2, 2012 Haskell’s notion of protocols are called “type classes,” and the designers of Swift have often stated that Swift’s protocols took a lot of inspiration from Haskell. This means that Haskellers run into a lot of the same problems we do when writing abstractions with type classes. In this article Gabriella Gonzalez lays down the case for scrapping type classes and just using simple datatypes. http://www.haskellforall.com/2012/05/scrap-your-type-classes.html Haskell Antipattern: Existential Typeclass Luke Palmer • Jan 24, 2010 A Haskell article that demonstrates a pattern in the Haskell community, and why it might be an anti-pattern. In a nutshell, the pattern is for libraries to express their functionality with typeclasses (i.e. protocols) and provide Any* wrappers around the protocol for when you do not want to refer to a particular instance of that protocol. The alternative is to replace the typeclass with a simple concrete data type. Sound familiar? https://lukepalmer.wordpress.com/2010/01/24/haskell-antipattern-existential-typeclass/ Some news about contramap Brandon Williams • Oct 29, 2018 A few months after releasing our episode on Contravariance we decided to rename this fundamental operation. The new name is more friendly, has a long history in mathematics, and provides some nice intuitions when dealing with such a counterintuitive idea. https://www.pointfree.co/blog/posts/22-some-news-about-contramap Contravariance Julie Moronuki & Chris Martin This article describes the ideas of contravariance using the Haskell language. In many ways exploring functional programming concepts in Haskell is “easier” because the syntax is sparse and allows you to focus on just the core ideas. https://typeclasses.com/contravariance Protocol Witnesses: App Builders 2019 Brandon Williams • May 3, 2019 Brandon gave a talk about “protocol witnesses” at the 2019 App Builders conference. The basics of scraping protocols is covered as well as some interesting examples of where this technique really shines when applied to snapshot testing and animations. Note Protocol-oriented programming is strongly recommended in the Swift community, and Apple has given a lot of guidance on how to use it in your everyday code. However, there has not been a lot of attention on when it is not appropriate, and what to do in that case. We will explore this idea, and show that there is a completely straightforward and mechanical way to translate any protocol into a concrete datatype. Once you do this you can still write your code much like you would with protocols, but all of the complexity inherit in protocols go away. Even more amazing, a new type of composition appears that is difficult to see when dealing with only protocols. We will also demo a real life, open source library that was originally written in the protocol-oriented way, but after running into many problems with the protocols, it was rewritten entirely in this witness-oriented way. The outcome was really surprising, and really powerful. https://www.youtube.com/watch?v=3BVkbWXcFS4 Downloads Sample code 0035-advanced-protocol-witnesses-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 .