世界需要另一个 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})`