Video #39: Witness-Oriented Library Design
Episode: Video #39 Date: Nov 26, 2018 Access: Members Only 🔒 URL: https://www.pointfree.co/episodes/ep39-witness-oriented-library-design

Description
We previously refactored a library using protocols to make it more flexible and extensible but found that it wasn’t quite as flexible or extensible as we wanted it to be. This week we re-refactor our protocols away to concrete datatypes using our learnings from earlier in the series.
Video
Cloudflare Stream video ID: bfaf7b55eeef7f1e17756fd2fb182fa7 Local file: video_39_witness-oriented-library-design.mp4 *(download with --video 39)*
References
- Discussions
- Overture
- Protocol-Oriented Programming in Swift
- Modern Swift API Design
- Protocols with Associated Types
- uber/ios-snapshot-test-case
- Snapshot Testing in Swift
- Scrap your type classes
- Haskell Antipattern: Existential Typeclass
- Protocol Witnesses: App Builders 2019
- 2019 App Builders
- Pullback
- 0039-witness-oriented-library-design
- Brandon Williams
- Stephen Celis
- Mastodon
- GitHub
- CC BY-NC-SA 4.0
- source code
- MIT License
Transcript
— 0:05
Previously we refactored a small snapshot testing library that worked with images of UIView s and we generalized it to work with even more types by using protocols. This first let us write tests against values of any type that could produce an image! We conformed UIImage , CALayer , and UIViewController and instantly unlocked the ability to write snapshot tests against them. None of these types worked with our original API.
— 0:41
We then generalized it even further to work with any kind of snapshot format, not just images! This gave us the ability to write snapshot tests against blobs of text, and by the end of the episode we had a truly impressive, flexible library that we could have continued to extend in interesting ways.
— 1:06
However it wasn’t all roses. We hit a bunch of protocol-oriented bumps along the way: we butted heads with complicated features like capital Self requirements, dodged naming collisions, and recovered some ergonomics by way of protocol extensions and default conformances (oh my!). We finally hit the ultimate bump that stopped us in our tracks: types can only conform to protocols a single time, so our library is limited by the language to a single snapshot format per type. This may not seem like a big deal. As a community we do so much with protocols and they really don’t seem like a limiting factor in our APIs. However, we saw that even the type we started with, UIView s, had multiple useful conformances, including screen shots of how the views render and text-based descriptions of the view’s state and hierarchy.
— 2:17
Luckily, over the past weeks we’ve built up the muscles to convert protocols to concrete datatypes and functions, and we’ve seen firsthand how it helps us solve this multiple conformances problem! So let’s re-refactor our library and see what plain ole datatypes and functions can do for us. A recap
— 2:35
We previously defined a couple snapshot tests. class PointFreeFrameworkTests: SnapshotTestCase { func testEpisodesView() { let episodesVC = EpisodeListViewController(episodes: episodes) assertSnapshot(matching: episodesVC) } func testGreeting() { let greeting = """ Welcome to Point-Free! ----------------------------------------- A Swift video series exploring functional programming and more. """ assertSnapshot(matching: greeting) } }
— 2:45
When we run these tests, they pass because they found a matching reference on disk that was previously recorded.
— 2:55
We designed this library with protocols, so let’s give those protocols another look. protocol Diffable { static func diff( old: Self, new: Self ) -> (String, [XCTAttachment])? static func from(data: Data) -> Self var data: Data { get } } This is the protocol we ended up with that described that ability to diff two values of a datatype. It came with a diff function such that when the old and new values do not perfectly match we return a failure message and an array of attachments for Xcode. And it came with a way to serialize and deserialize these values to Data , which allows us to save and load snapshots.
— 3:40
In the past we have shown that there is a completely algorithmic way of translating protocols into structs, so let’s define a concrete Diffing type that represents the Diffable protocol. struct Diffing<A> { let diff: (A, A) -> (String, [XCTAttachment])? let from: (Data) -> A let data: (A) -> Data }
— 5:05
We’ve seen that almost all of these structs are generic over at least one parameter that represents the Self of the protocol. What’s interesting is how often this formulation can help clear away the fog of that implicit Self . We can now much more easily see that the from and data functions have a very similar structure: one produces A s from Data and the other produces Data from A s, a duality that was much more difficult to see at-a-glance with implicit self and the syntactic differences between functions and properties.
— 6:03
What’s it look like to create a “witness” of the Diffing type? Well, we can use a trick from earlier where we extend Diffing , constrained to its generic, and define them as statics. We can start by translating our String: Diffable conformance to a value of Diffing . extension String: Diffable { static func diff( old: String, new: String ) -> (String, [XCTAttachment])? { guard let difference = Diff.lines(old, new) else { return nil } return ( "Diff: …\n\(difference)", [XCTAttachment(string: difference)] ) } static func from(data: Data) -> String { return String(decoding: data, as: UTF8.self) } var data: Data { return Data(self.utf8) } } extension Diffing where A == String { static let lines = Diffing( pathExtension: "txt", diff: { old, new in guard let difference = Diff.lines(old, new) else { return nil } return ( "Diff:\n\(difference)", [XCTAttachment(string: difference)] ) }, from: { data in String(decoding: data, as: UTF8.self) }, to: { string in Data(string.utf8) } ) } We can call it lines since we’re diffing by line and can imagine other Diffing values that diff strings in different ways. Mostly we copy-paste from our earlier conformance and do a little bit of renaming, but otherwise the logic is exactly the same.
— 7:21
One protocol down, one to go! Last week we also defined a Snapshottable protocol, which had an associated Diffable type: protocol Snapshottable { associatedtype Snapshot: Diffable static var pathExtension: String { get } var snapshot: Snapshot { get } }
— 7:48
We learned a couple weeks back how to translate protocols with associated types into concrete datatypes, and it was simply to introduce another generic. struct Snapshotting<A, Snapshot> { } Here the A represents the Self , while the Snapshot generic represents the associated type. Now we can translate things easily enough: struct Snapshotting<A, Snapshot> { let diffing: Diffing<Snapshot> let pathExtension: String let to: (A) -> Snapshot } It’s important to note how we translated the Snapshot associated type into this diffing value, because we haven’t ever quite made this translation before. Whenever an associated type is constrained to a protocol, as the Snapshot type is constrained to Diffable , then the concrete representation of this value is a witness itself! In this case we can capture the Diffable associated type constraint as a concrete Diffing value generic in B .
— 9:28
What does it take to make a Snapshotting witness on String? We can first take a look at our existing conformance: extension String: Snapshottable { static let pathExtension = "txt" var snapshot: String { return self } }
— 9:43
And we can translate it over to the witness world: extension Snapshotting where A == String, Snapshot == String { static let lines = Snapshotting( diffing: .lines, pathExtension: "txt", snapshot: { $0 } ) } Not much! We were able to use our .lines diffing witness and snapshot is just { $0 } since A and Snapshot are the same type.
— 10:31
This looks a little busier than our earlier, protocol-based conformance, which doesn’t have to make its associated type explicit. We could have made it more explicit with a type alias: extension String: Snapshottable { typealias Snapshot = String static let pathExtension = "txt" var snapshot: Snapshot { return self } } Now we have something that maps more explicitly to our concrete datatype, but we can also see that the explicitness of our datatype is much more flexible because we could have swapped out lines for another Diffing value.
— 11:00
Alright, we’ve been able to take the muscles we built over our series on protocol witnesses and exercise them! We have a bunch of new value types representing our protocols, and a few conformances to boot! Asserting with witnesses Now that we have our Diffing and Snapshotting concrete datatypes and a couple of witnesses, we should be able to translate our assertSnapshot helper.
— 11:42
First, let’s update the signature. func assertSnapshot<A, Snapshot>( matching value: A, as witness: Snapshotting<A, Snapshot>, file: StaticString = #file, function: String = #function, line: UInt = #line) { We no longer constrain our generic A to Snapshottable , but instead pass in an unconstrained value and a witness.
— 12:35
Now we just need to fix all of our errors. let snapshot = value.snapshot Value of type ‘A’ has no member ‘snapshot’ We can update this by asking our witness for the snapshot. let snapshot = witness.snapshot(value)
— 12:54
Next, we have the path extension requirement. let referenceUrl = snapshotUrl(file: file, function: function) .appendingPathExtension(S.pathExtension) Use of unresolved identifier ‘S’; did you mean ‘A’? We can now pluck pathExtension directly off our witness. let referenceUrl = snapshotUrl(file: file, function: function) .appendingPathExtension(witness.pathExtension)
— 13:09
Next we have our requirement that we can extract a snapshot from data. let reference = S.Snapshot.from(data: referenceData) Use of unresolved identifier ‘S’; did you mean ‘A’? We reach into our snapshotting witness’s diffing witness to do just that. let reference = witness.diffing.from(referenceData)
— 13:36
Another Diffable call to do the actual diffing needs to be updated. guard let (message, attachments) = S.Snapshot .diff(old: reference, new: snapshot) else { return } Use of unresolved identifier ‘S’; did you mean ‘A’? We again reach into the diffing witness. guard let (message, attachments) = witness.diffing .diff(reference, snapshot) else { return }
— 13:55
Finally, we need to update our requirement that converts snapshots into data. try! snapshot.data.write(to: referenceUrl) Value of type ‘Snapshot’ has no member ‘data’ And one last time, we reach into the diffing witness. try! witness.diffing.data(snapshot).write(to: referenceUrl)
— 14:08
Not a lot had to change! We removed a generic constraint and we added a generic parameter in order to pass along Snapshotting values. Then we replaced all of our protocol-based calls to use our witness instead.
— 14:21
Now we just have to update our snapshot strings test to use a witness! func testGreeting() { let greeting = """ Welcome to Point-Free! ----------------------------------------- A Swift video series exploring functional programming and more. """ // assertSnapshot(matching: greeting) assertSnapshot(matching: greeting, as: .lines) }
— 14:41
And this reads really nicely: assert that there’s a snapshot matching our greeting as lines.
— 14:51
We run the test, and it still passes! Diffing images with witnesses
— 15:04
So what about image-based snapshot test against view controllers? We’ll need to add witnesses to Diffing and Snapshotting for images and views.
— 15:24
An image Diffing witness is largely a matter of copying and pasting code. extension Diffing where A == UIImage { static let image = Diffing( pathExtension: "txt", diff: { old, new in guard let difference = Diff.images(old, new) else { return nil } return ( "Expected old@\(old.size) to match new@\(new.size)", [old, new, difference].map(XCTAttachment.init) ) }, from: { data in UIImage(data: data, scale: UIScreen.main.scale)! }, data: { image in image.pngData()! } ) } And we no longer have to deal with the self.init and final class confusion we had with our protocolized version. We can just call directly to a UIImage initializer.
— 16:34
So we have a witness on Diffing for UIImage , what about Snapshotting ? extension Snapshotting where A == UIImage, Snapshot == UIImage { static let image = Snapshotting( diffing: .image, pathExtension: "png", snapshot: { $0 } ) }
— 17:20
We are now completely capable of snapshotting UIImage s in a witness-oriented fashion, but what about all the other UIKit types? Well, let’s start with CALayer . extension Snapshotting where A == CALayer, Snapshot == UIImage { static let image = Snapshotting( diffing: .image, pathExtension: "png", snapshot: { layer in return UIGraphicsImageRenderer(size: layer.bounds.size) .image { ctx in layer.render(in: ctx.cgContext) } } ) }
— 18:24
Now we can snapshot CALayer s using our new witness system. So what about UIView ? extension Snapshotting where A == UIView, Snapshot == UIImage { static let image = Snapshotting( diffing: .image, pathExtension: "png", snapshot: { view in Snapshotting<CALayer, UIImage>.image.to(view.layer) } ) } It was a bit trickier since we wanted to reuse our CALayer logic, but we were able to pluck it out from our existing Snapshotting witness.
— 19:52
Finally, let’s make a witness for UIViewController . extension Snapshotting where A == UIViewController, Snapshot == UIImage { static let image = Snapshotting( diffing: .image, pathExtension: "png", snapshot: { vc in Snapshotting<UIView, UIImage>.view.to(vc.view) } ) } Again, we can reach into our existing Snapshotting witness on views to handle the snapshot logic.
— 20:47
Alright, so this all seems a bit verbose compared to our protocol version, but let’s at least make sure it works. func testEpisodesView() { let episodesVC = EpisodeListViewController(episodes: episodes) // assertSnapshot(matching: episodesVC) assertSnapshot(matching: episodesVC, as: .image) }
— 21:09
It compiles and tests still pass! Our witnesses are doing everything our protocols were doing. At this point we could delete all the protocols and bid them adieu! Pulling back witnesses
— 21:26
We can collect all of our UIKit witnesses in one place to look at a strangeness. They don’t all fit on the screen at once, but it’s close.
— 22:02
There’s a lot of commonality here. Each witness uses the .image diffing strategy and "png" as the path extension. The real work is always happening in the snapshot. And half of our witnesses call out to logic from other witnesses.
— 22:34
There’s something we can do here to clean all of this up using a concept we’ve covered numerous times in the past: the pullback ! We want to have the ability to take snapshots on some types and pull them back into snapshots on other types. struct Snapshotting<A, Snapshot> { … func pullback<A0>( _ f: @escaping (A0) -> A ) -> Snapshotting<A0, Snapshot> { return Snapshotting<A0, Snapshot>( diffing: self.diffing, pathExtension: self.pathExtension, snapshot: { a0 in self.snapshot(f(a0)) } ) } }
— 24:40
Pulling back snapshots really just means to apply a transformation to the thing we want to snapshot by producing a thing we already know how to snapshot.
— 24:50
So let’s use it! We can redefine our CALayer snapshot by pulling back the idea of snapshotting an image to a larger structure, the CALayer , and project the layer into a UIImage by rendering it into a context. extension Snapshotting where A == CALayer, Snapshot == UIImage { static let image: Snapshotting = Snapshotting<UIImage, UIImage> .image.pullback { layer in return UIGraphicsImageRenderer(size: layer.bounds.size) .image { ctx in layer.render(in: ctx.cgContext) } } // static let image = Snapshotting( // diffing: .image, // pathExtension: "png", // snapshot: { layer in // return UIGraphicsImageRenderer(size: layer.bounds.size) // .image { ctx in layer.render(in: ctx.cgContext) } // } // ) }
— 25:28
And from here we can pull back snapshotting on UIView s! We can take the idea of snapshotting a layer, pull it back to work with UIView s by getting its layer. extension Snapshotting where A == UIView, Snapshot == UIImage { static let image: Snapshotting = Snapshotting<CALayer, UIImage> .image.pullback { view in view.layer } // static let image = Snapshotting( // diffing: .image, // pathExtension: "png", // snapshot: { view in // Snapshotting<CALayer, UIImage>.image.to(view.layer) // } // ) }
— 25:57
And we can further pull back snapshotting on UIViewController ! We pull the UIView witness back to work with view controllers by getting the view controller’s view. extension Snapshotting where A == UIViewController, Snapshot == UIImage { static let image: Snapshotting = Snapshotting<UIView, UIImage> .image.pullback { vc in vc.view } // static let image = Snapshotting( // diffing: .image, // pathExtension: "png", // snapshot: { vc in // Snapshotting<UIView, UIImage>.view.to(vc.view) // } // ) }
— 26:23
It’s now very apparent that all of these conformances are composed from one another, a fact that was much more hidden in the protocol world, where we had no choice but to manually conform each type to Snapshottable .
— 26:49
We also have Overture in our workspace, so we can make pulling back some of these snapshots even nicer. For example, views: static let image: Snapshotting = Snapshotting<CALayer, UIImage> .image.pullback(get(\.layer))
— 27:13
Now it reads really nicely: to snapshot UIView s as an image, you just take image snapshotting on CALayer and pull it back by getting the view’s layer.
— 27:29
We can do the same for view controllers: static let image: Snapshotting = Snapshotting<CALayer, UIImage> .image.pullback(get(\.view))
— 27:37
Let’s delete all the commented-out code and look at everything. extension Snapshotting where A == UIImage, Snapshot == UIImage { static let image = Snapshotting( diffing: .image, pathExtension: "png", snapshot: { $0 } ) } extension Snapshotting where A == CALayer, Snapshot == UIImage { static let image: Snapshotting = Snapshotting<UIImage, UIImage> .image.pullback { layer in return UIGraphicsImageRenderer(size: layer.bounds.size) .image { ctx in layer.render(in: ctx.cgContext) } } } extension Snapshotting where A == UIView, Snapshot == UIImage { static let image: Snapshotting = Snapshotting<CALayer, UIImage> .image.pullback(get(\.layer)) } extension Snapshotting where A == UIViewController, Snapshot == UIImage { static let image: Snapshotting = Snapshotting<UIView, UIImage> .image.pullback(get(\.view)) }
— 27:45
And now all four of our witnesses fit on the screen at once and it’s very clear what they’re doing and how they’re composed from one another. Pullback was the key!
— 27:56
It’s also much easier to see now how our previous conformances were composed. In the witness world, pullback shows that we were taking existing conformances and building new ones with them, though there was nothing in the syntax that made this super clear! Multiple witnesses
— 28:34
We’ve now fully ported our protocol-oriented work to concrete datatypes. Now we’re free to finally do the thing that we couldn’t do with protocols. Define multiple witnesses for a single type!
— 28:50
Let’s take a look at our Snapshottable conformance on UIView . extension UIView: Snapshottable { var snapshot: UIImage { return self.layer.snapshot } } We’ve picked and chosen a conformance based on the UIImage format, but we also showed a completely valid alternative using a text description of the view hierarchy. extension UIView: Snapshottable { // var snapshot: UIImage { // return self.layer.snapshot // } var snapshot: String { self.setNeedsLayout() self.layoutIfNeeded() return ( self.perform(Selector(("recursiveDescription")))? .takeUnretainedValue() as! String ) .replacingOccurrences( of: ":?\\s*0x[\\da-f]+(\\s*)", with: "$1", options: .regularExpression ) } }
— 30:14
While protocols have this restriction where we can’t have both conformances at the same time, witnesses do not! Let’s define a witness on the recursive description of a UIView . extension Snapshotting where A == UIView, Snapshot == String { static let recursiveDescription = Snapshotting( diffing: .lines, pathExtension: "txt", snapshot: { view in view.setNeedsLayout() view.layoutIfNeeded() ( view.perform(Selector(("recursiveDescription"))) .takeUnretainedValue() as! String ) .replacingOccurrences( of: ":?\\s*0x[\\da-f]+(\\s*)", with: "$1", options: .regularExpression ) } ) }
— 31:19
All we need now is a quick lil pullback for our view controller! extension Snapshotting where A == UIViewController, Snapshot == String { static let recursiveDescription: Snapshotting = Snapshotting<UIView, String> .recursiveDescription.pullback(get(\.view)) }
— 32:07
To snapshot on a view controller’s recursive description is to take our existing snapshot on views and pull it back to view controllers and getting their views.
— 32:21
And now we can add an additional assertion to our view controller test! We can assert not only against its pixel data, but we can assert against its recursive description! func testEpisodesView() { let episodesVC = EpisodeListViewController() assertSnapshot(matching: episodesVC, as: .image) assertSnapshot(matching: episodesVC, as: .recursiveDescription) }
— 32:36
It builds, tests run, and they pass! The recursive description we recorded last time is a reference on disk and the logic didn’t change.
— 32:56
There’s one thing to note though. Our recursiveDescription snapshot on UIView s was defined from scratch, but we can pull it back from .lines ! extension Snapshotting where A == UIView, Snapshot == String { static let recursiveDescription: Snapshotting = Snapshotting<String, String>.lines.pullback { view in view.setNeedsLayout() view.layoutIfNeeded() ( view.perform(Selector(("recursiveDescription"))) .takeUnretainedValue() as! String ) .replacingOccurrences( of: ":?\\s*0x[\\da-f]+(\\s*)", with: "$1", options: .regularExpression ) } }
— 33:59
We took our Snapshotting witness on a string’s lines and pulled it back to work with UIView s by projecting into their recursive description! This was a composition that we probably wouldn’t have noticed in the protocol world! What’s the point?
— 34:15
We’ve now solved the biggest problem that protocols have: we can’t conform a single type to the same protocol multiple times. With witnesses, we’ve defined multiple witnesses for a single type! And they’re all very useful witnesses to have. We’re beginning to see just how powerful this concept is.
— 34:33
So what’s the point? Well, the past three episodes are a big “what’s the point?” We’ve taken a case study of actual library code and shown how it can be designed around protocols and compared it to how it can be designed around concrete data types, functions, and witnesses.
— 34:46
We’ve answered the question, but let’s take things one step further and show an example of why having multiple witnesses is a good idea.
— 35:01
It’s really easy to demonstrate this. Currently, both of our view controller snapshots pass, but what if we were refactoring our view controller and we forgot to add back a specific view that happens to be hidden in our snapshot?
— 35:38
If we go back and run our test, we get a failure! But we get a failure for our text-based snapshot only. The image snapshot didn’t fail because it doesn’t test that hidden label.
— 36:13
So we now have this extra test coverage on this view controller. Not only are we capturing pixel data as an image, we’re also capturing the view hierarchy: the abstract tree structure of the view with all of their properties. If we were working in the protocol world, we’d have to choose a single conformance and miss out on the benefits of the one we didn’t pick.
— 36:58
This feature alone is probably enough to sell the idea of witnesses. People should be looking to concrete types and witnesses to guide their library design! It can unlock a lot of potential that protocols would block. But it goes further! By embracing types and witnesses we uncovered a beautiful composition: the pullback. And this allowed us to focus on snapshotting very small, well-known things, and pulling them back to more and more complicated structures.
— 37:31
So what we’ve come up with here is a very composable, transformable library based on some very well-understood ideas, but it completely eschews our community’s best practice ideals of “protocol-oriented” design. We’re told that we should be sprinkling protocols everywhere and that it will make our libraries easier to use, but we’ve found that is not the case at all, and here’s a shining example!
— 38:14
And it doesn’t end here. Switching from protocols to witnesses unlocks even more functionality that would be impossible with protocols.
— 38:36
When we set out to refactor our library, we knew we’d see some benefits, but we were surprised when other benefits materialized along the way. That’ll have to wait till next time! 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 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 uber/ios-snapshot-test-case Uber, previously Facebook Facebook released a snapshot testing framework known as FBSnapshotTestCase back in 2013, and many in the iOS community adopted it. The library gives you an API to assert snapshots of UIView ’s that will take a screenshot of your UI and compare it against a reference image in your repo. If a single pixel is off it will fail the test. Since then Facebook has stopped maintaining it and transfered ownership to Uber. https://github.com/uber/ios-snapshot-test-case Snapshot Testing in Swift Stephen Celis • Sep 1, 2017 Stephen gave an overview of snapshot testing, its benefits, and how one may snapshot Swift data types, walking through a minimal implementation. https://www.stephencelis.com/2017/09/snapshot-testing-in-swift 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/ 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 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 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 Downloads Sample code 0039-witness-oriented-library-design 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 .