Witness-Oriented Library Design

Episode #39 • Nov 26, 2018 • Subscriber-Only

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.

Witness-Oriented Library Design
Introduction
00:05
A recap
02:35
Asserting with witnesses
11:25
Diffing images with witnesses
15:04
Pulling back witnesses
21:26
Multiple witnesses
28:34
What’s the point?
34:15

Unlock This Episode

Our Free plan includes 1 subscriber-only episode of your choice, plus weekly updates from our newsletter.

Introduction

Previously we refactored a small snapshot testing library that worked with images of UIViews 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.

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.

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, UIViews, had multiple useful conformances, including screen shots of how the views render and text-based descriptions of the view’s state and hierarchy.

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.

This episode is for subscribers only.

Subscribe to Point-Free

Access this episode, plus all past and future episodes when you become a subscriber.

See plans and pricing

Already a subscriber? Log in

Exercises

  1. Take our witness-oriented library and define some interesting strategies! Think about your own code base and specialized Snapshotting (and Diffing) instances you can define. Here are some suggestions to get you started!

    • Define a dump strategy on Snapshotting<Any, String> that uses the output of Swift’s dump function. You can reuse logic from the recursiveDescription strategy to remove occurrences of memory addresses.

    • Define a Snapshotting<URLRequest, String> strategy that snapshots a raw HTTP request, pretty-printing the method, headers, and body of the request.

    • Define a Snapshotting<NSAttributedString, UIImage> strategy that snapshots images of attributed strings.

    • Define a Snapshotting<NSAttributedString, String> strategy that snapshots HTML representations of attributed strings.

References

Protocol-Oriented Programming in Swift

Apple • Tuesday 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.

Modern Swift API Design

Apple • Wednesday 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.

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.

Protocols with Associated Types

Alexis Gallagher • Tuesday 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.

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.

Snapshot Testing in Swift

Stephen Celis • Friday 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.

Scrap your type classes

Gabriel Gonzalez • Wednesday 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 Gabriel Gonzalez lays down the case for scrapping type classes and just using simple datatypes.

Haskell Antipattern: Existential Typeclass

Luke Palmer • Sunday 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?

Protocol Witnesses: App Builders 2019

Brandon Williams • Friday 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.

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.

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.

Some news about contramap

Brandon Williams • Monday 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.