世界需要另一个 Swift Either 类型吗?不需要。我们是不是患了“非我发明”综合症?也许吧。我们仍然认为这是个好主意吗?绝对是。
Either 是许多函数式和强类型语言中的一个概念,它允许在一个字段中存储要么一种类型,要么另一种类型的值。
/// A response for the population query
struct PopulationResponse {
/// The list of people in the population
///
/// - Note: In 1.x, this was a list of names as `String`s.
/// In 2.x and newer, this is a map of UUIDs to `Person` objects
let people: Either<[String], [UUID: Person]>
}
这个实现带来了一些优势
如果 Either 的 Left 和 Right 类型也遵循某些协议,则此实现会自动使 Either 的实例遵循这些协议。
目前,支持以下协议:
Equatable – 带来 == 和 !=。 当 Left 和 Right 是不同的类型时,它认为 left 永远不等于 right。 当这些类型相等时,它忽略 left 和 right 的位置。
Comparable – 带来 <, <=, >=, 和 >。 当位置的类型不相等时,它认为 left 既不小于也不大于 right。当这些类型相等时,它忽略位置。
Hashable – 允许 Either 的实例透明地获得与其包含值相同的哈希值。
CustomStringConvertible – 提供一个 .description 字段,其值与 Either 包含值的 .description 字段相同。
CustomDebugStringConvertible – 提供一个 .debugDescription 字段,其值与 Either 包含值的 .debugDescription 字段相同。
Codable – 允许 Either 实例被编码。 这会产生一个多值键控容器,该容器只包含一个键值对,其中键是 "left" 或 "right",而值是实例的值编码后的结果。
{
"either": {
"left": {
"name": "Dax",
"favoriteColor": 6765239
}
}
}
或者
{
"either": {
"right": 42
}
}
显然,你最终需要从中获取一个值,它提供了几种方法:
left – 如果 Either 是 .left,则返回该值,否则返回 nil。right – 如果 Either 是 .right,则返回该值,否则返回 nil。当 Left 和 Right 都是相同的类型时,以下也可用:
value – 当前值,不考虑该值是 .left 还是 .right。* – 受 C 语言中解引用指针的语义启发 (并且因为 Swift 不允许自定义后缀 !),在 Either 实例前放置此符号,其行为与调用 `.value` 相同。func name(_ user: Either<Person, Person>) -> String {
return (*user).name
}
Value – 由于两个位置的类型相同,此类型别名允许您引用该类型,而无需专门使用 Left 或 Right。typealias LegacyOrMigratedUser = Either<User, User>
func account(of user: LegacyOrMigratedUser) -> LegacyOrMigratedUser.Value.Account {
(*user).account
}
这提供了各种映射 Either 的方法。通常,这些方法将其视为只有一个元素的集合,类似于 Optional 如何被视为包含 0 或 1 个元素的集合。
map(left:right:) — 将此 either 的两个位置映射到不同的值/类型,无论其当前值如何。每次调用此函数时,只会调用其中一个回调(映射一个值的那个),但这允许您重复使用相同的调用多次来映射两侧,具体取决于设置了哪一侧。
map(left:) – 仅将此 either 的 Left 位置映射到不同的值/类型。 只有当此 either 是 .left 时才会调用回调。
map(right:) – 仅映射 Right 位置。与 map(left:) 相反。
这允许您将某些类型的实例转换为 Either,然后再转换回来。
Optional – 任何 Left 是 Void 的 Either 都可以转换为 Optional<Right>,反之,任何 Optional 都可以转换为 Either<Void, Wrapped>。 只需将一个传递给另一个的初始化程序即可。
let either = Either<Void, String>.right("I'm valued")
let optional = Optional(either)
print(optional!) // Prints `I'm valued`
var optional: String? = nil
var either = Either<Void, _>(optional)
print(either) // Prints `left()`
optional = "I'm not sorry"
either = .init(optional)
print(either) // Prints `right("I\'m not sorry")`
Result – 当 Either 的 Right 是 Error 时,您可以将其与 Result 相互转换,类似于上面的 Optional 转换。
let either = Either<Data, Error>.left(Data(base64Encoded: "SG93ZHk=")!)
let result = Result(either)
print(result) // Prints `success(5 bytes)`
var result = Result<Data, Error>(catching: { try Data(contentsOf: URL(string: "https://example.com")!) })
var either = Either<_, Error>(result)
print(either) // Prints `left(1256 bytes)`
result = .init(catching: { try Data(contentsOf: URL(string: "https://fakeDomain.fakeTld")!) })
either = .init(result)
print(either) // Prints `right(Error Domain=NSCocoaErrorDomain Code=256 "The file couldn’t be opened." UserInfo={NSURL=https://fakeDomain.fakeTld})`