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
和 |>
的实现
<*>
<*
*>
>>-
<|>
|>
<>