Unlock This Episode
Our Free plan includes 1 subscriber-only episode of your choice, plus weekly updates from our newsletter.
Introduction
In our most recent episode on algebraic data types, we explored how we can use algebra to understand generics and recursive data structures in the Swift type system. Things got pretty abstract! We grounded the episode, though, in a useful type that doesn’t seem to get enough attention: the non-empty list—in fact, we gave it twice as much attention because math led us to two different formulas!
We deal with non-empty data regularly, but without a structure at hand it’s all too easy to reach for Array
and inevitably we end up writing a bit of extra code to handle those empty cases. Today’s episode is all about embracing type-safe guarantees, avoiding those extra code paths, and building an ergonomic API over the non-empty list. Let’s build a low-friction, non-empty type suitable for your production code base!
Subscribe to Point-Free
Access this episode, plus all past and future episodes when you become a subscriber.
Already a subscriber? Log in
Exercises
Why shouldn’t
NonEmpty
conditionally conform toSetAlgebra
when its underlying collection type also conforms toSetAlgebra
?Define the following method:
extension NonEmpty where C: SetAlgebra { func contains(_ member: C.Element) -> Bool }
Note that
SetAlgebra
does not require that elements are equatable, so utilize other methods onSetAlgebra
to make this check.Define the following method:
extension NonEmpty where C: SetAlgebra { func union(_ other: NonEmpty) -> NonEmpty }
Ensure that no duplicate
head
element enters the resulting non-empty set. The following property should hold true:NonEmptySet(1, 2, 3).union(NonEmptySet(3, 2, 1)).count == 3
Define a helper subscript on
NonEmpty
to access a non-empty dictionary’s element using the dictionary key. You can constrain the subscript overKey
andValue
generics to make this work.Our current implementation of
NonEmpty
allows for non-empty dictionaries to contain thehead
key twice! Write a constrained extension on NonEmpty to prevent this from happening. You will need create aDictionaryProtocol
forDictionary
in order to make this work because Swift does not currently support generic extentions.// Doesn't work extension <Key, Value> NonEmpty where Element == [Key: Value] {} // Works protocol DictionaryProtocol { /* Expose necessary associated types and interface */ } extension Dictionary: DictionaryProtocol {} extension NonEmpty where Element: DictionaryProtocol {}
Look to the standard library APIs for inspiration on how to handle duplicate keys, like the
init(uniqueKeysAndValues:)
andinit(_:uniquingKeysWith:)
initializers.Define
updateValue(_:forKey:)
on non-empty dictionaries.Define
merge
andmerging
on non-empty dictionaries.Swift
Sequence
contains twojoined
methods that flattens a nested sequence given an optional separator sequence. For example:["Get ready", "get set", "go!"].joined("...") // "Get ready...get set...go!" [[1], [1, 2], [1, 2, 3]].joined([0, 0]) // [1, 0, 0, 1, 2, 0, 0, 1, 2, 3]
A non-empty collection of non-empty collections, when joined, should also be non-empty. Write a
joined
function that does so. How must the collection be constrained?Swift
Sequence
also contains twosplit
methods that split aSequence
into[Sequence.SubSequence]
. They contain a parameter,omittingEmptySubsequences
that prevents non-empty sub-sequences from being included in the resulting array.Splitting a non-empty collection, while omitting empty subsequences, should return a non-empty collection of non-empty collections. Define this version of
split
onNonEmpty
.What are some challenges with conditionally-conforming
NonEmpty
toEquatable
? Consider the following check:NonEmptySet(1, 2, 3) == NonEmptySet(3, 2, 1)
. How can these challenges be overcome?Define
zip
on non-empty arrays:func zip<A, B>(_ a: NonEmpty<[A]>, _ b: NonEmpty<[B]>) -> NonEmpty<[(A, B)]> {}
References
NonEmpty
Brandon Williams & Stephen Celis • Wednesday Jul 25, 2018NonEmpty
is one of our open source projects for expressing a type safe, compiler proven non-empty
collection of values.
Validated
Brandon Williams & Stephen Celis • Friday Aug 17, 2018Validated
is one of our open source projects that provides a Result
-like type, which supports a zip
operation. This means you can combine multiple validated values into a single one and accumulate all of
their errors.