NonEmpty 0.2.2

NonEmpty 0.2.2

Brandon WilliamsStephen CelisStephen Celis维护。



NonEmpty 0.2.2

🎁NonEmpty

Swift 5 Build Status @pointfreeco

确保集合包含值的编译时保证。

动机

我们经常处理那些理论上永远不应该为空的集合,但类型系统并没有提供这样的保证,所以我们不得不处理空的情况,通常是通过 ifguard 语句。NonEmpty 是一种轻量级类型,可以将任何集合类型转换为非空版本。以下是一些例子

// 1.) A non-empty array of integers
let xs = NonEmpty<[Int]>(1, 2, 3, 4)
xs.first + 1 // `first` is non-optional since it's guaranteed to be present

// 2.) A non-empty set of integers
let ys = NonEmpty<Set<Int>>(1, 1, 2, 2, 3, 4)
ys.forEach { print($0) } // => 1, 2, 3, 4

// 3.) A non-empty dictionary of values
let zs = NonEmpty<[Int: String]>((1, "one"), [2: "two", 3: "three"])

// 4.) A non-empty string
let helloWorld = NonEmpty<String>("H", "ello World")
print("\(helloWorld)!") // "Hello World!"

应用

非空集合类型有许多应用,但由于 Swift 标准库没有提供这种类型,这可能很难看到。以下只是其中一些应用

强化第一方 API

许多 API 收到并返回可能为空的数组,而实际上它们可以保证数组不为空。考虑一个 groupBy 函数

extension Sequence {
  func groupBy<A>(_ f: (Element) -> A) -> [A: [Element]] {
    // Unimplemented
  }
}

Array(1...10)
  .groupBy { $0 % 3 }
// [0: [3, 6, 9], 1: [1, 4, 7, 10], 2: [2, 5, 8]]

然而,返回类型 [A: [Element]] 中的数组 [Element] 可以保证永不空,因为生成 A 的唯一方式是从 Element 生成。因此,该函数的签名可以被强化,如下所示:

extension Sequence {
  func groupBy<A>(_ f: (Element) -> A) -> [A: NonEmpty<[Element]>] {
    // Unimplemented
  }
}

与第三方 API 的更好接口

有时我们与第三方API交互时,需要非空值的集合,因此在我们的代码中应使用非空类型,以确保不会向API发送空值。一个很好的例子是GraphQL。下面是一个非常简单的查询构建器和打印器。

enum UserField: String { case id, name, email }

func query(_ fields: Set<UserField>) -> String {
  return (["{"] + fields.map { "  \($0.rawValue)" } + ["}"])
    .joined()
}

print(query([.name, .email]))
// {
//   name
//   email
// }

print(query([]))
// {
// }

最后一个查询是程序员错误,会导致GraphQL服务器返回错误,因为发送空查询是无效的。通过强制我们的查询构建器与空集一起工作,我们可以防止这种情况的发生。

func query(_ fields: NonEmptySet<UserField>) -> String {
  return (["{"] + fields.map { "  \($0.rawValue)" } + ["}"])
    .joined()
}

print(query(.init(.name, .email)))
// {
//   name
//   email
// }

print(query(.init()))
// 🛑 Does not compile

更丰富的数据结构

Swift社区(以及其他语言社区中)的一个流行类型是Result类型。它允许你表达一个可能是成功或失败的价值。还有一个相关的类型也很有用,称为Validated类型。

enum Validated<Value, Error> {
  case valid(Value)
  case invalid([Error])
}

Validated类型的值要么是有效的,并附带有Value,要么是无效的,并附带有描述该值所有错误情况的错误数组。例如

let validatedPassword: Validated<String, String> =
  .invalid(["Password is too short.", "Password must contain at least one number."])

这很有用,因为它允许你描述价值的所有错误,而不仅仅是其中一个。然而,如果我们使用一个空数组作为错误列表,这并没有太多意义。

let validatedPassword: Validated<String, String> = .invalid([]) // ???

相反,我们应该加强Validated类型,使其使用非空数组。

enum Validated<Value, Error> {
  case valid(Value)
  case invalid(NonEmptyArray<Error>)
}

现在这是一个编译器错误

let validatedPassword: Validated<String, String> = .invalid(.init([])) // 🛑

安装

Carthage

如果你使用Carthage,你可以在你的Cartfile中添加以下依赖项

github "pointfreeco/swift-nonempty" ~> 0.2

CocoaPods

如果你的项目使用CocoaPods,只需在你的Podfile中添加以下内容

pod 'NonEmpty', '~> 0.2'

SwiftPM

如果您想在一款使用SwiftPM的项目中使用NonEmpty,只需在您的Package.swift文件中添加一个dependencies子句即可。

dependencies: [
  .package(url: "https://github.com/pointfreeco/swift-nonempty.git", from: "0.2.0")
]

Xcode 子项目

将NonEmpty作为子模块克隆,或下载它,并将NonEmpty.xcodeproj文件拖拽到您的项目中。

想了解更多?

这些概念及更多内容在Point-Free中得到了详尽的探讨,这是一个由Brandon WilliamsStephen Celis主持的探讨函数式编程和Swift的视频系列。

NonEmpty首次在第20集中被探索。

video poster image

许可证

所有模块均在MIT许可证下发布。有关详细信息,请参阅LICENSE