今日は、ユトレヒト大学助教授はWouter Swierstraに入社し、彼はある機能スウィフトの共著者。彼の作品の一つの領域は、関数型プログラミングで、彼はそれが、このようなスウィフトが支配的な言語になってきているように、時間の長い期間のために働いてきた、この地域からのアイデアのいくつかを見て喜んでいました。
私たちは、いくつかの集中での関数型プログラミングのウサギの穴を議論します。具体的には、私たちが焦点を当てますreduce
。自分自身を思い出させるために何をreduce
、いくつかの例を書いてみましょう。
例の削減
我々は呼んで、1から10までの数字を保持する配列を作成しreduce
、アレイの最大数を見つけるためにそれを。結果の初期値は、一緒に結果と組み合わせて、個々の配列要素と機能:この関数は2つのパラメータを取ります。私たちは小さな渡されたInt
初期値を、我々は、max
機能の組み合わせを使用しました。
let numbers = Array(1...10)
numbers.reduce(Int.min, max) // 10
复制代码
また、することができreduce
、ゼロと渡すことによって、+
オペレータをすべての要素の合計を計算します
numbers.reduce(0, +) // 55
复制代码
さんが詳しく見てみましょうreduce
関数のシグネチャではArray
:
func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Self.Element) throws -> Result) rethrows -> Result
复制代码
ここでの関数Result
型は汎用的です。上記の2つの例では、結果と配列要素の種類Int
のタイプがあり、必ずしもタイプと一致しません。例えば、我々はまたできreduce
アレイ要素を含むかどうかを決定するために使用されます。reduce
携帯電話の結果がありますBool
:
extension Sequence where Element: Equatable {
func contains1(_ el: Element) -> Bool {
return reduce(false) { result, x in
return x == el || result
}
}
}
numbers.contains1(3) // true
numbers.contains1(13) // false
复制代码
我々は呼んでreduce
最初の結果をfalse
配列が空の場合、それは結果でなければならないので、。複合機能では、我々は、入ってくる要素は、我々が探している、または結果はこれまでの等しい要素に等しいチェックtrue
。
このバージョンはcontains
、ほとんどない効率的な、それはそれが必要以上に多くの作業を行いますので、。しかし、使用を見つけることは興味深い運動を達成することですreduce
。
リスト
しかしreduce
から来ますか?私たちは、単一のリストを定義することができますし、reduce
それにその起源のルックアップ操作を探索します。
スウィフトでは、空のリストを含んでいる大文字と小文字を区別し、非空リストで、列挙リストとして定義されます。従来、非空の場合として知られているcons
単一のリスト要素関連値とテールです。尾は再帰的なケースを許可する別のリスト、である、我々は間接的としてマークする必要があります。
enum List<Element> {
case empty
indirect case cons(Element, List)
}
复制代码
次のように我々は、整数のリストを作成できます。
let list: List<Int> = .cons(1, .cons(2, .const(3, .empty)))
复制代码
その後、我々はと呼ばれる関数を定義するfold
、のように見えますreduce
が、それは少し違います。
extension List {
func fold<Result>(_ emptyCase: Result, _ consCase: (Element, Result) -> Result) -> Result {
}
}
复制代码
これら二つの引数fold
と2例の一致は偶然ではありませんList
。関数の実装では、我々は、各パラメータとそれに対応するケースを使用します。
extension List {
func fold<Result>(_ emptyCase: Result, _ consCase: (Element, Result) -> Result) -> Result {
switch self {
case .empty:
return emptyCase
case let .cons(x, xs):
return consCase(x, xs.fold(emptyCase, consCase))
}
}
}
复制代码
今、私たちはできるfold
リスト内の要素の合計を計算します。
list.fold(0, +) // 6
复制代码
また、できるfold
リストの長さを見つけるために使用すること:
list.fold(0, { _, result in result + 1 }) // 3
复制代码
デモではfold
との宣言との対応がありますList
。
私たちは例を列挙することができますList
1は、空のリストを構築することであり、他方が非空のリストを構築することである:2つの方法のリストを構築することとみなします。
そしてfold
のための1:2つのパラメータがある.empty
場合、について.cons
-私たちは、それぞれの場合の結果を計算するために必要な情報の場合は。
我々が考える場合、そのemptyCase
引数は、値のタイプではありませんResult
が、関数として() -> Result
、その後、.empty
コンストラクタの間に明確な対応になります。
折りたたみと減少
fold
機能はほぼ同じですreduce
が、1つの小さな違いが、。我々は2つの関数を呼び出すことにより、両者の違いを証明し、その結果を比較することができます。
まず、我々は呼んでfold
、2例のコンストラクタを転送List
引数として:
ダンプ(list.fold(List.empty、List.cons))
/*
▿ __lldb_expr_4.List<Swift.Int>.cons
▿ cons: (2 elements)
- .0: 1
▿ .1: __lldb_expr_4.List<Swift.Int>.cons
▿ cons: (2 elements)
- .0: 2
▿ .1: __lldb_expr_4.List<Swift.Int>.cons
▿ cons: (2 elements)
- .0: 3
- .1: __lldb_expr_4.List<Swift.Int>.empty
*/
复制代码
私たちは、元のリストとまったく同じ結果を参照してください。換言すれば、fold
変化なし2つのケースのコンストラクタを使用して同一の製造方法が複雑な関数である呼び出します。
その後、我々はreduce
、アレイ、同じ着信コンストラクタList
-私たちは交換しなければならない以外はcons
最初のため、オーダーパラメータケースをreduce
通過し、第二の電流要素の積算結果を渡します。
dump(Array(1...3).reduce(List.empty, { .cons($1, $0) }))
/*
▿ __lldb_expr_6.List<Swift.Int>.cons
▿ cons: (2 elements)
- .0: 3
▿ .1: __lldb_expr_6.List<Swift.Int>.cons
▿ cons: (2 elements)
- .0: 2
▿ .1: __lldb_expr_6.List<Swift.Int>.cons
▿ cons: (2 elements)
- .0: 1
- .1: __lldb_expr_6.List<Swift.Int>.empty
*/
复制代码
私たちは、このチェックボックスをオンにするとreduce
、時間に電話した結果を、我々はそれを参照してくださいList
逆の順序では理由は、配列の要素が含まれているreduce
配列を通って、各要素が結果に加工されます。これは違うものですfold
、それはリストを右に左にあるため、とだけ、emptyCase
この値を使用している場合、それはリストの最後に到達したとき。
そこな和や長さを計算するなど、多くの操作が、ある、reduce
とfold
同じ結果が得られます。しかし、重要な操作の順序によって、我々は2つの関数の動作の違いを見始めます。
List.reduce
我々は達成したfold
私たちはスウィフトを使用している、 Array.reduce
しかし、その実装を見ても非常に興味深いですList.reduce
。私たちは、同じパラメータを拡張で関数を記述し、それらを与えますfold
:
extension List {
func reduce<Result>(_ emptyCase: Result, _ consCase: (Element, Result) -> Result) -> Result {
// ...
}
}
复制代码
これを達成するために、我々はなりますemptyCase
初期の結果にパラメータを割り当て、その後、我々はそれが空であるかどうかを確認するために、リストを切り替えます。それが空の場合、我々はすぐに結果を返すことができます。リストが空でない場合、我々はなりますx
我々は、日付に使用される要素を追加consCase
参照するには、関数の結果を、私たちは、再帰呼び出しreduce
尾、累積転送の結果を:
extension List {
func reduce<Result>(_ emptyCase: Result, _ consCase: (Element, Result) -> Result) -> Result {
let result = emptyCase
switch self {
case .empty:
return result
case let .cons(x, xs):
return xs.reduce(consCase(x, result), consCase)
}
}
}
复制代码
末尾再帰
ここでは、それがあることがわかりますreduce
末尾再帰的である:それはどちらかすぐに再帰呼び出し、結果を返しますどちらか。fold
それが呼び出すので、末尾再帰ではなく、consCase
機能を、多かれ少なかれ、隠れ再帰と二番目のパラメータの機能を構築するために使用されます。
この違いは、私たちが今、二つの方法を比較して、よりはっきりと見ることができ、異なる結果につながりますList
:
let list: List<Int> = .cons(1, .cons(2, .const(3, .empty)))
list.fold(List.empty, List.cons) // .cons(1, .cons(2, .const(3, .empty)))
list.reduce(List.empty, List.cons) // .cons(3, .cons(2, .const(1, .empty)))`
复制代码
末尾再帰的操作を使用すると、簡単にサイクルを書き換えることができます。
extension List {
func reduce1<Result>(_ emptyCase: Result, _ consCase: (Element, Result) -> Result) -> Result {
var result = emptyCase
var copy = self
while case let .cons(x, xs) = copy {
result = consCase(x, result)
copy = xs
}
return result
}
}
复制代码
このバージョンreduce1
によって生成された結果reduce
:
list.reduce1(List.empty, List.cons) // .cons(3, .cons(2, .cons(1, .empty)))
复制代码
reduce
操作はほんの一例である折りたたみ、我々は他の多くの構造で、実際にこれらの操作を定義することができます。