🛂 validated
一个累加多个错误的结果类型。
目录
动机
问题
Swift的异常处理在第一次失败时中断。由于这一点,它并不适合处理如表单数据这样的情况,其中多个输入可能导致多个错误。
struct User {
let id: Int
let email: String
let name: String
}
func validate(id: Int) throws -> Int {
guard id > 0 else {
throw Invalid.error("id must be greater than zero")
}
return id
}
func validate(email: String) throws -> String {
guard email.contains("@") else {
throw Invalid.error("email must be valid")
}
return email
}
func validate(name: String) throws -> String {
guard !name.isEmpty else {
throw Invalid.error("name can't be blank")
}
return name
}
func validateUser(id: Int, email: String, name: String) throws -> User {
return User(
id: try validate(id: id),
email: try validate(id: email),
name: try validate(id: name)
)
}
我们将几个投掷函数组合成一个可以返回User
的单一投掷函数。
let user = try validateUser(id: 1, email: "[email protected]", name: "Blob")
// User(id: 1, email: "[email protected]", name: "Blob")
如果id
、email
或name
无效,将会抛出错误。
let user = try validateUser(id: 1, email: "[email protected]", name: "")
// throws Invalid.error("name can't be blank")
不幸的是,如果这些输入中的几个或所有都是无效的,第一个错误将会胜出。
let user = try validateUser(id: -1, email: "blobpointfree.co", name: "")
// throws Invalid.error("id must be greater than zero")
使用Validated处理多个错误
Validated
是一种类似于Result
的类型,可以累积多个错误。而不是使用投掷函数,我们可以定义与Validated
一起工作的函数。
func validate(id: Int) -> Validated<Int, String> {
return id > 0
? .valid(id)
: .error("id must be greater than zero")
}
func validate(email: String) -> Validated<String, String> {
return email.contains("@")
? .valid(email)
: .error("email must be valid")
}
func validate(name: String) -> Validated<String, String> {
return !name.isEmpty
? .valid(name)
: .error("name can't be blank")
}
为了累积错误,我们使用了一个可能我们已经很熟悉的功能:zip
。
let validInputs = zip(
validate(id: 1),
validate(email: "[email protected]"),
validate(name: "Blob")
)
// Validated<(Int, String, String), String>
Validated
上的zip
函数在序列上的工作方式与在序列中使用的基本相同,但它不是将一对序列zip到一个由对组成的序列中,而是将一组单个的Validated
值压缩成一个组内单独的Validated
值。
从这里,我们可以使用另一个我们可能已经很熟悉的功能,map
,它接受一个转换函数并产生一个新的其有效情况已被转换的Validated
值。
let validUser = validInputs.map(User.init)
// valid(User(id: 1, email: "[email protected]", name: "Blob"))
我们有效的输入组已经转换成了一个有效的用户。
为了便捷性和组合性,提供了一个柯里化的zip(with:)
函数,它接受一个转换函数和Validated
输入。
zip(with: User.init)(
validate(id: 1),
validate(email: "[email protected]"),
validate(name: "Blob")
)
// valid(User(id: 1, email: "[email protected]", name: "Blob"))
无效的输入会在invalid
情况下产生一个错误。
zip(with: User.init)(
validate(id: 1),
validate(email: "[email protected]"),
validate(name: "")
)
// invalid(["name can't be blank"])
更重要的是,多个无效输入会产生一个包含多个错误的invalid
情况。
zip(with: User.init)(
validate(id: -1),
validate(email: "blobpointfree.co"),
validate(name: "")
)
// invalid([
// "id must be greater than zero",
// "email must be valid",
// "name can't be blank"
// ])
无效的错误被保存在一个非空数组中,以确保在编译时不会遇到空的invalid
情况。
安装
Carthage
如果你使用Carthage,可以将以下依赖项添加到你的Cartfile
中
github "pointfreeco/swift-validated" ~> 0.2.1
CocoaPods
如果您的项目使用CocoaPods,只需将其添加到您的Podfile
中。
pod 'PointFree-Validated', '~> 0.2.1'
SwiftPM
如果您想在使用SwiftPM的项目中使用Validated,只需在您的Package.swift
中添加一个dependencies
子句即可。
dependencies: [
.package(url: "https://github.com/pointfreeco/swift-validated.git", from: "0.2.1")
]
Xcode 子项目
子模块、克隆或下载验证过的版本,并将 Validated.xcodeproj
拖动到你的项目中。
想了解更多信息?
这些概念(以及更多)在由 Brandon Williams 和 Stephen Celis 主办的 Point-Free 视频系列中进行了深入探讨,该视频系列探讨了函数式编程和 Swift。
Validated 在 The Many Faces of Zip: Part 2 中进行了探讨
许可证
所有模块均在 MIT 许可证下发布。有关详细信息,请参阅 LICENSE。