一个为函数式类型和操作设计的框架,旨在自然地融入 Swift。
作为一种具有一等函数的语言,Swift 支持将函数用作值。这意味着函数可以存储在变量中并作为参数传递给其他函数。
您可能在使用序列时已经遇到过 Swift 的一些函数式 API
let numbers = 1...5
let incrementedNumbers = numbers.map { $0 + 1 } // [2, 3, 4, 5, 6]
let evenNumbers = numbers.filter { $0 % 2 == 0 } // [2, 4]
假设我们正在为用户的姓名输入进行一些简单的输入清理。我们可能会分几个步骤进行
let name = nameTextField.text
let withoutExtraWhitespace = removeExtraWhitespace(name)
let withoutEmojis = removeWeirdUnicodeCharacters(withoutExtraWhitespace)
let properlyCapitalized = capitalizeProperly(withoutEmojis)
这看起来像是助手函数的工作。让我们快速编写一个
func sanitize(name: String) -> String {
let withoutExtraWhitespace = removeExtraWhitespace(name)
let withoutEmojis = removeWeirdUnicodeCharacters(withoutExtraWhitespace)
let properlyCapitalized = capitalizeProperly(withoutEmojis)
return properlyCapitalized
}
您甚至可以选择用一行代码编写它
func sanitize(name: String) -> String {
return properlyCapitalized(withoutEmojis(removeExtraWhitespace(name)))
}
不幸的是,当我们对同一个输入调用更多函数时,我们似乎会陷入以下两个问题之一,具体取决于我们的方法
虽然优化器应确保两种情况下的功能相同,但选项 1 感觉不必要地冗长,而选项 2 严重阻碍了代码从左到右的可读性。
FunctionKit 允许我们使用组合以简单、清晰和声明式的方式重写此函数
let sanitize = Function.pipeline(removeExtraWhitespace, removeWeirdUnicodeCharacters, capitalizeProperly)
let sanitizedNames = names.map(sanitize)
这里发生了什么?
在您生命中的某个时候的数学课程中,您可能被介绍过函数组合的概念
(g ∘ f)(x) = g(f(x))
基本思想是通过获取一个函数的输出并将其用作另一个函数的输入来创建新函数。
我们使用 Function
类型来包装 Swift 函数,并为其提供强大的新功能——双关语。静态 pipeline
方法将每个函数的输出向前传递到下一个函数。piped(into:)
实例方法对单个函数执行相同的操作。
我们也可以使用组合来转换类型
let sanitizedCount = sanitize.piped(into: { $0.count })
let sanitizedNameCounts = names.map(sanitizedCount) // [Int]
通过将函数用作可组合的、可转换的单元,我们增强了模块化和表达性。 FunctionKit 提供了许多工具来简化函数式类型的使用。
Function
对象中,它可以通过清晰、易于发现的实例方法访问强大的函数式操作,例如组合和柯里化。Function
对象作为输入的 Function
方法具有重载以直接支持 Swift 函数,并且像 Sequence.map
这样的原生 Swift 方法具有重载以接受 Function
对象。结果是一个更简单、更直观、更清晰的 API。重要提示: 将 Swift 变成纯函数式编程语言不是 FunctionKit 的目标。 FunctionKit 以一种自然地融入语言的方式拥抱并增强了 Swift 的函数式功能。
FunctionKit 倾向于迭代语言的方法点语法,而不是自由函数或运算符。有关 Swift 中函数式编程构造的更传统应用,请参阅 Overture、Prelude 和 Swiftz。
FunctionKit 的主要单元是 Function
类型,它包装了一个 Swift 函数。使用其初始化器创建一个 Function
let makeRandom = Function(arc4random_uniform) // Function<UInt32, UInt32>
let stringFromData = Function(String.init(data:encoding:)) // Function<(Data, String.Encoding), String?>
let increment = Function { (x: Int) in x + 1 } // Function<Int, Int>
或者,使用本节稍后描述的静态方法之一,通过组合几个 Swift 函数来初始化 Function
。
要调用 Function
,请使用 apply(_:)
方法。
let random = makeRandom.apply(100) // 42, perhaps
let parsed = stringFromData.apply(Data(), .utf8) // Optional<String>.some("")
let incremented = increment.apply(6) // 7
一旦包装在 Function
中,通往强大的函数式 API 的大门就敞开了。
Function
类型支持以下函数式操作
前向组合是通过将一个函数的输出管道输送到另一个函数来创建新函数的过程。前向组合的过程可以描述为
pipe
(A) -> B
(B) -> C
=>
(A) -> C
要前向组合函数,请使用 piped(into:)
方法
let sanitize = Function(removeExtraWhitespace).piped(into: capitalizeProperly) // Function<String, String>
可以使用静态 pipeline
方法前向组合一系列函数
let sanitizedCount = Function.pipeline(removeExtraWhitespace, capitalizeProperly, { $0.count }) // Function<String, Int>
串联是输入和输出类型相同的函数的前向组合。串联的过程可以描述为
concatenate
(A) -> A
(A) -> A
=>
(A) -> A
虽然此功能完全由正常的前向组合提供,但在串联的调用点立即清楚类型保持不变。因此,串联是增强类型安全性和意图清晰度的有价值的操作。
要串联函数,请使用 concatenated(with:)
方法
let sanitize = Function(removeExtraWhitespace).concatenated(with: capitalizeProperly) // Function<String, String>
可以使用静态 concatenation
方法串联一系列函数
let sanitize = Function.concatenation(removeExtraWhitespace, removeWeirdUnicodeCharacters, capitalizeProperly) // Function<String, String>
链式调用是返回 Optional
值的函数的前向组合。如果链中的任何函数返回 nil
,则整个函数返回 nil
。链式调用的过程可以描述为
chain
(A) -> B?
(B) -> C?
=>
(A) -> C?
要链式调用函数,请使用 chained(with:)
方法
let urlStringHost = Function(URL.init(string:)).chained(with: { $0.host }) // Function<String, String?>
可以使用静态 chain
方法链式调用一系列返回 Optional
值的函数
let urlStringHostFirstCharacter = Function.chain(URL.init(string:), { $0.host }, { $0.first }) // Function<String, Character?>
后向组合是通过将一个函数应用于另一个函数的输出来创建新函数的过程。后向组合的过程可以描述为
compose
(B) -> C
(A) -> B
=>
(A) -> C
虽然当参数顺序相反时,此功能完全由前向组合提供,但有时使用后向组合编写代码更具表达力。将后向组合视为将一个类型上的函数“提升”到另一个类型上的函数可能很有用。
要后向组合函数,请使用 composed(with:)
方法
let sanitize = Function(capitalizeProperly).composed(with: removeExtraWhitespace) // Function<String, String>
可以使用静态 composition
方法后向组合一系列函数
let sanitizedCount = Function.composition({ $0.count }, removeExtraWhitespace, capitalizeProperly) // Function<String, Int>
柯里化是将接受元组输入参数的函数拆分为一系列函数的过程。柯里化一个双参数函数的过程可以描述为
curry
(A, B) -> C
=>
(A) -> (B) -> C
柯里化函数接受单个参数并返回一个函数。
柯里化对于部分应用函数很有用,即为函数的其中一个参数提供一个值,以生成一个少一个参数的函数。
例如,使用 curried()
方法,我们可以柯里化并部分应用整数加法
// CurriedTwoArgumentFunction<A, B, C> is a typealias for Function<A, Function<B, C>>.
let curriedAdd: CurriedTwoArgumentFunction<Int, Int, Int> = Function(+).curried()
let addToFive = curriedAdd.apply(5) // Function<Int, Int>
addToFive.apply(3) // 8
addToFive.apply(20) // 25
当部分应用函数时,使用 flippingFirstTwoArguments()
方法翻转其参数的顺序可能会有所帮助
// In describing the steps below, standard Swift function notation will be used over `Function` type notation
// to demonstrate the operations performed more clearly.
let utf8StringFromData =
Function(String.init(data:encoding:)) // (Data, String.Encoding) -> String?
.curried() // (Data) -> (String.Encoding) -> String?
.flippingFirstTwoArguments() // (String.Encoding) -> (Data) -> String?
.apply(.utf8) // (Data) -> String?
虽然柯里化函数通常提供最大的灵活性,但取消柯里化柯里化函数可能很有用。取消柯里化两个参数的过程可以描述为
uncurry
(A) -> (B) -> C
=>
(A, B) -> C
例如,使用 uncurried()
方法,我们可以取消柯里化未应用的 method reference
let stringHasPrefix = String.hasPrefix // (String) -> (String) -> Bool
let uncurriedHasPrefix = Function(stringHasPrefix).uncurried() // Function<(String, String), Bool>
uncurriedHasPrefix.apply("function", "func") // true
注意: 如果 SE-0042 得到实施,则未应用的方法引用的行为可能会发生变化。
静态 get
方法接受 KeyPath<Root, Value>
并返回一个从根提取值的函数。
// The following two functions have the same effect:
let getStringCount1: Function<String, Int> = .init { $0.count }
let getStringCount2 = Function.get(\String.count)
静态 update
方法接受 WritableKeyPath<Root, Value>
并返回一个 setter 函数,该函数将类型属性的更新传播到该类型实例的更新。
struct Person {
var name: String
}
let updateName = Function.update(\Person.name) // Function<Function<String, String>, Function<Person, Person>>
let lowercaseName = updateName.apply { $0.lowercased() } // Function<Person, Person>
let MICHAEL = Person(name: "MICHAEL")
let michael = lowercaseName.apply(MICHAEL)
// michael.name == "michael"
警告: 将 update
生成的函数与可变引用类型一起使用可能会导致意外行为。
某些函数类型因其在常见任务(例如过滤和排序)中的用途而特别常见。 FunctionKit 为以下类型提供了额外的 API
Consumer
类型定义为
typealias Consumer<Input> = Function<Input, Void>
Consumer
类型描述了不产生输出的函数,例如修改状态或记录数据的函数。 Consumer
实例可以使用 then(_:)
方法进行链式调用
let handleError = Consumer<Error>
.init(presentError)
.then(analyticsManager.logError)
Consumer
类型适用于与可变引用类型一起使用
let configureLabel = Consumer<UILabel>
.init(stylizeFont)
.then { $0.numberOfLines = 0 }
.then(view.addSubview)
configureLabel.apply(detailLabel)
注意: Consumer
不 旨在模拟 inout
函数,后者会改变值类型。 单独的类 存在用于此目的。
Provider
类型定义为
typealias Provider<Output> = Function<Void, Output>
Provider
类型描述了可以产生输出而无需传递输入的工厂方法。可以使用 make()
方法调用它们
let timestampProvider = Provider(Date.init)
let now = timestampProvider.make()
let idProvider = Provider(IdentifierFactory.makeId)
let id = idProvider.make()
Predicate
类型定义为
typealias Predicate<Input> = Function<Input, Bool>
Predicate
实例对于验证输入和过滤很有用。可以使用 test(_:)
方法调用它们,使用 negated()
方法或前缀 !
运算符对其进行否定,并使用中缀 &&
和 ||
运算符进行逻辑组合。
由于某些谓词非常常见,因此还提供了其他静态函数,例如 isEqualTo(_:)
、isLessThan(_:)
和 isInRange(_:)
。
let hasValidLength: Predicate<String> = Function
.get(\String.count)
.piped(into: .isInRange(4...12))
let usesValidCharacters = Predicate<String>
.init { $0.contains(where: invalidCharacters.contains) }
.negated()
let isValidUsername = hasValidLength && usesValidCharacters
也可以使用静态 all(of:)
和 any(of:)
方法创建 Predicate
实例
let isOddPositiveMultipleOfThree: Predicate<Int> =
.all(of:
{ $0 % 2 != 0 },
{ $0 > 0 },
{ $0 % 3 == 0 }
)
(-15...15).filter(isOddPositiveMultipleOfThree) // [3, 9, 15]
Comparator
类型定义为
typealias Comparator<T> = Function<(T, T), Foundation.ComparisonResult>
Comparator
实例对于比较同一类型的两个值很有用,尤其是在排序时。它们可以通过多种方式创建
Comparable
类型上使用静态 naturalOrder()
和 reverseOrder()
方法创建 Comparator
。Comparable
属性之一的基础上使用静态 comparing(by:)
方法创建 Comparator
。Optional
Comparable
属性之一的基础上使用静态 nilValuesFirst(by:)
和 nilValuesLast(by:)
方法创建 Comparator
。创建后,Comparator
实例可以
thenComparing(by:)
方法进行排序。reversed()
方法反转。lifting(with:)
方法提升为另一种类型的 Comparator
。struct User {
let id: Int
let signupDate: Date
let email: String?
}
// Compares `User` instances, where
// - emails are compared lexicographically, with `nil` values coming after non-`nil` values
// - ties (i.e. two emails are the same, or both are `nil`) are broken by comparing the users' ids, with the lower id coming first.
let userEmailThenId = Comparator<User>
.nilValuesLast(by: { $0.email })
.thenComparing(by: { $0.id })
let sortedUsers = users.sorted(by: userEmailThenId)
可以使用静态 sequence
方法从该类型上的一系列 Comparator
实例创建该类型的 Comparator
。
// Compares `User` instances, where
// - users who signed up earlier come first
// - if users signed up at the exact same time, their emails are compared lexicographically
// - if users' emails are identical or both `nil`, the user with the lower id comes first
let userSignupDateThenEmailThenId: Comparator<User> =
.sequence(
.comparing(by: { $0.signupDate }),
.nilValuesLast(by: { $0.email }),
.comparing(by: { $0.id })
)
类型为 (inout A) -> Void
的函数可以使用 InoutFunction
建模,InoutFunction
是一种与 Function
分开的类型,它提供了连接 inout 函数的能力。
可以使用 toInout()
方法将 Function<A, A>
转换为 InoutFunction<A>
,并使用 withoutInout()
方法转换回来
let increment = Function { (x: Int) in x + 1 } // Function<Int, Int>
let inoutIncrement = increment.toInout() // InoutFunction<Int>
var x = 1
inoutIncrement.apply(&x) // x == 2
inoutIncrement.apply(&x) // x == 3
即将发布的更新将支持抛出异常函数 - 请稍后查看!
将以下行添加到您的 Cartfile
github "mpangburn/FunctionKit" ~> 0.1.0
将以下行添加到您的 Podfile
pod 'FunctionKit', '~> 0.1.0'
将以下行添加到您的 Package.swift 文件
.package(url: "https://github.com/mpangburn/FunctionKit", from: "0.1.0")
FunctionKit 在 MIT 许可证下发布。有关详细信息,请参阅 LICENSE。