Type erasure with AnyError
NADocumentPicker
returns a Future<T, E: ErrorType>
with the type:
Future<NSURL, AnyError>
What is AnyError
in the above code? AnyError
provides a unified concrete error type for a Future
. A unified error type is necessary when composing futures with flatMap
so, if one of the futures fails, the error can be propagated through the compositional chain.
For example:
@IBAction func pickerButtonPressed(sender: UIButton) {
let urlFuture = NADocumentPicker.show(from: sender, parentViewController: self)
let documentFuture = urlFuture.flatMap { url in MyDocument(url).open() }
documentFuture.onSuccess { document in
print("Opened document: \(document)")
}
}
The Future
returned by open()
has to have an identical error type to allow the compositional chain to transform:
Future<NSURL, AnyError> /*to*/ Future<MyDocument, AnyError>
// NSURL to MyDocument
It would not be possible to compose with flatMap
if the transformation was:
Future<NSURL, NADocumentPickerErrors> /*to*/ Future<MyDocument, MyDocumentErrors>
// can't use flatMap to compose with incompatible ErrorTypes
The above composition is not possible even-though all error types derive from ErrorType
.
Why not use ErrorType
directly as Future<NSURL, ErrorType>
? Unfortunately Swift does not allow protocols to be used as concrete type parameters and generates the error:
Using ‘ErrorType’ as a concrete type conforming to protocol ‘ErrorType’ is not allowed
AnyError
AnyError
conforms to ErrorType
and wraps any type conforming to ErrorType
, allowing different error types to be treated uniformly:
public struct AnyError : ErrorType {
public let cause:ErrorType
public init(cause:ErrorType) {
self.cause = cause
}
}
Type Erasure
This pattern of using AnyError
as a generic concrete error type is known “type erasure”. The Swift standard library contains a few other type erasing wrappers, for example AnySequence<T>
:
AnySequence
wraps any sequence with element type T, conforms to SequenceType itself, and forwards all operations to the wrapped sequence. When handling the wrapper, the specific type of the wrapped sequence is fully hidden.
AnySequence
is used below to “erase” the origin of the sequence generated from a zip operation:
let sequence1 = zip("hello".characters, "world".characters)
sequence1.dynamicType // Zip2Sequence<String.CharacterView, String.CharacterView>.Type
let sequence2 = AnySequence(sequence1)
sequence2.dynamicType // AnySequence<(Character, Character)>.Type
See also
- AnyError on GitHub
- Cocoaphony: A Little Respect for AnySequence
- Cannot return a future with a different error type by chaining calls with flatMap
- Define a Swift protocol which requires a specific type of sequence
AnySequence
type erasure forZip3Sequence
- Values and errors, part 1: ‘Result’ in Swift
- Superpowers / obfuscation with map & flatMap