Prelude 是一个由 Wayfair 开发的库,用于 Swift 中的函数式编程。我们在 我们的应用 中使用这个库,以函数式风格构建功能。
可以通过 Carthage 或 Swift Package Manager 在您的项目中安装 Prelude。CocoaPods 的支持即将推出。
如果您通过 Carthage 集成 Prelude,并且需要调整构建设置(例如,您可能希望将库构建为静态框架,以提高应用启动速度),我们包含了一个对特殊自定义文件的引用,这可能对您有所帮助。
如果还有其他我们可以做的事情来支持将 Prelude 集成到您的项目中,请提出 issue!
inout 和非 inout 用法继续阅读了解详情,或直接前往 playground
Changeable 是一种包装类型,允许您将 hasChanged 标志与数据片段捆绑在一起。如果您需要告知某人一个值是否已更改,请使用它
let wasUpdated = Changeable(hasChanged: true, value: "foo")
当 Changeable 用作函数的返回值时,它会变得更有用。您可以轻松地向调用者传达他们对您的调用是否为空操作
struct User { var firstName: String }
/// return a new `User` value with the first name changed to "hamburgers".
/// If the first name is already "hamburgers", do nothing.
func updateFirstName(_ user: User) -> Changeable<User> {
if user.firstName == "hamburgers" {
return Changeable(hasChanged: false, value: user)
} else {
let newUser = User(firstName: "hamburgers")
return Changeable(hasChanged: true, value: newUser)
}
}
let myUser = User(firstName: "hamburgers")
updateFirstName(myUser) // .hasChanged => false
如果您需要转换值的多个方面,请编写一系列小的转换函数,使用 flatMap 将它们链接在一起,然后在最后检查是否有任何更改
/// return a new `User` value with the first name changed to "hamburgers".
/// If the first name is already "hamburgers", do nothing.
func updateFirstName(_ user: User) -> Changeable<User> {
/* implementation omitted */
}
/// return a new `User` value with the last name changed to "kale".
/// If the last name is already "kale", do nothing.
func updateLastName(_ user: User) -> Changeable<User> {
/* implementation omitted */
}
let myUser = User(firstName: "hamburgers", lastName: "kale")
Changeable(value: myUser)
.flatMap(updateFirstName)
.flatMap(updateLastName) // .hasChanged => false
let someoneElse = User(firstName: "peter", lastName: "kale")
Changeable(value: someoneElse)
.flatMap(updateFirstName)
.flatMap(updateLastName) // .hasChanged => true
>>- 只是 flatMap 的同义词。它的工作方式相同,但您不必键入那么多括号
let myUser = User(firstName: "hamburgers", lastName: "kale")
Changeable(value: myUser)
>>- updateFirstName
>>- updateLastName // .hasChanged => false (same code as above)
像 updateFirstName 这样手工编写转换函数可能很耗时。对于快速更新,请使用 Changeable.write 来生成转换函数,就像上面那样,从 Swift 键路径构建。
let myUser = User(firstName: "hamburgers", lastName: "kale")
Changeable(value: myUser)
>>- Changeable.write("hamburgers", at: \.firstName)
>>- Changeable.write("kale", at: \.lastName) // .hasChanged => false (same overall transformation as above)
但是,复杂的更改可能需要比上面的简单语法更多。此代码片段使用来自封闭作用域的标志来确定是否写入其更改之一
let makeTheChange = true
let myUser = User(firstName: "hamburgers", lastName: "kale")
Changeable(value: myUser)
>>- Changeable.write("peter", at: \.firstName, shouldChange: { _, _ in makeTheChange })
>>- Changeable.write("kale", at: \.lastName)
如果您的 Changeable 值是 var,您可以直接对其进行 .write 操作,而无需进行任何 flatMap 或 >>- 操作。此代码片段还使用函数式简写 pure 来快速将普通的 Person 值“提升”为 Changeable 值
var mutateMe = pure(Person(firstName: "hamburgers", lastName: "kale"))
mutateMe.write("hamburgers", at: \.firstName)
mutateMe.write("kale", at: \.lastName)
mutateMe.hasChanged // => false
Sequence.reduce 是一个强大的函数,但传递给它的函数通常只是内联编写的。由于这些函数可能非常有用,请将它们包装在 Reducer 中以命名它们、共享它们和传递它们
let sumOfIntegers: Reducer<Int, Int> = .nextPartialResult { sum, integer in
return sum + integer
}
[1, 2, 3].reduce(0, sumOfIntegers) // => 6
如果类型匹配,则可以通过将小的 reducer 链接在一起来构建更大的 reducer。它们按顺序执行
let productOfIntegers: Reducer<Int, Int> = .nextPartialResult { product, integer in
return product * integer
}
let bigReducer = sumOfIntegers.followed(by: productOfIntegers)
[1, 2, 3].reduce(0, bigReducer) // => 27
<> 只是 followed(by:) 的同义词,并且可以证明此操作是结合律的。因此,当您想要将许多 reducer 链接在一起时,请使用 <>,并且无需使用括号
let appendIntValue: Reducer<[String], Int> = .nextPartialResult { arr, integer in arr + ["\(integer)"] }
let appendIntValuePlus1: Reducer<[String], Int> = .nextPartialResult { arr, integer in arr + ["\(integer + 1)"] }
let appendIntValuePlus2: Reducer<[String], Int> = .nextPartialResult { arr, integer in arr + ["\(integer + 2)"] }
[1, 10, 100].reduce(
[],
appendIntValue <> appendIntValuePlus1 <> appendIntValuePlus2
) // => ["1", "2", "3", "10", "11", "12", "100", "101", "102"]
如果您有一个 reducer 需要 X 类型的值,但您手头只有 Y 类型的值,请编写一个将 Y 转换为 X 的函数,然后使用 pullback 适配您的 reducer。适配后的 reducer 将能够处理新的值
let appendIntValue: Reducer<[String], Int> = .nextPartialResult { arr, integer in arr + ["\(integer)"] }
func getCount(of string: String) -> Int { return string.count }
let appendCountValue = appendIntValue.pullback(getCount) // this reducer has been adapted
["a", "aa", "aaa"].reduce([], appendCountValue) // => ["1", "2", "3"]
inout Reducer(闭包的第一个参数是可变的 reducer)可能非常方便。实际上,我们的 reducer 默认是 inout 的:
let reducer: Reducer<[String], String> = .init { arr, item in arr.append(item) }
(请注意此处使用了 .init 而不是之前示例中使用的 .nextPartialResult)
在底层,所有 reducer 都是 inout 类型的。这意味着无论 reducer 如何初始化,它都可以与其他所有 reducer 互操作
let reducer: Reducer<[String], String> = .init { arr, item in arr.append(item) }
let reduceCaps: Reducer<[String], String> = .nextPartialResult { arr, item in
return arr + [item.uppercased()]
}
let bigReducer = reducer <> reduceCaps // this is ok
["foo", "bar", "baz"].reduce([], bigReducer)
Prelude.swift 包含我们对 curry、const、flip 和 |> 的实现
<*><**>>>-<|>|><>