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
AnySequencetype erasure forZip3Sequence- Values and errors, part 1: ‘Result’ in Swift
- Superpowers / obfuscation with map & flatMap