reduce関数の起源を探ります

今日は、ユトレヒト大学助教授は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

私たちは例を列挙することができますList1は、空のリストを構築することであり、他方が非空のリストを構築することである: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この値を使用している場合、それはリストの最後に到達したとき。

そこな和や長さを計算するなど、多くの操作が、ある、reducefold同じ結果が得られます。しかし、重要な操作の順序によって、我々は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操作はほんの一例である折りたたみ、我々は他の多くの構造で、実際にこれらの操作を定義することができます。


オリジナル住所:talk.objc.io/episodes/S0 ...

おすすめ

転載: blog.csdn.net/weixin_34319374/article/details/91398000