Edited at

[Swift4] 遅延評価のすゝめ 〜 LazySequence/LazyCollection

More than 1 year has passed since last update.


mapなどによる無駄な処理

[Int]である配列の要素に1を加えて2を掛け最初の要素を取り出してみます。

処理の流れを見るために出力を付け加えています。


func add(_ i: Int) -> (Int) -> Int {
return {
print("<<", $0)
return $0 + i
}
}
func multiply(_ i: Int) -> (Int) -> Int {
return {
print($0 * i, ">>")
return $0 * i
}
}

let re = Array(0..<5).map(add(1)).map(multiply(2)).first

これを実行すると

<< 0

<< 1
<< 2
<< 3
<< 4
2 >>
4 >>
6 >>
8 >>
10 >>

と出力され、reには2が代入されます。

最初のmapで全ての要素に1を加え、続くmapで全ての要素に2をかけ、その後、最初の要素を取り出しています。

必要な値は

let re = (0 + 1) * 2

だけで計算可能であるのに、無意味な計算が多数行われます。

これを解決するのがLazySequence/LazyCollectionとそのサブタイプです。


遅延評価

先ほどのプログラムにちょっと追加をします。

let re = Array(0..<5).lazy.map(add(1)).map(multiply(2)).first

lazyを追加しただけです。

これを実行すると

<< 0

2 >>

と出力され、reには2が代入されます。

最初の要素、つまり

let re = (0 + 1) * 2

だけが計算されているのがわかります。

これはつまり、mapに渡された関数の実行はその場ではされず、実際に値が必要になった時必要な要素のみに、この場合はfirstが呼ばれた時に必要である最初の要素に対してのみ関数が実行されているのです。

このように、実際の値が必要になるまで、mapなどに与えられる関数の実行を遅延させるのが遅延評価であり、Swiftにおいてその役割を担っているのがLazySequence/LazyCollectionとそのサブタイプです。


いつ実行されるの?

LazySequence/LazyCollectionとそのサブタイプ自身はあまり覚える必要はありませんが、それらに対して、何をした時何が起こるのかというのは重要です。

遅延評価させるつもりで書いたものが、遅延評価になっていなかったというのが一番問題なのです。

ということで、未完成ですが、僕が現在調べた結果を記しておきます。

ハイライトされて読みやすくなるようにメソッドにはfuncをプロパティにはvarをつけています。


実行されない

func map(_:)

func flatMap(_:)

func filter(_:)

func enumerated()

func reversed()

func prefix(_:)
func prefix(through:)
func prefix(upTo:)
func prefix(while:)

func suffix(_:)
func suffix(from:)

func dropFirst()
func dropFirst(_:)
func dropLast()
func dropLast(_:)

func joined()


該当箇所のみ実行される

var first

var last


条件が満たされるまで/満たされている間実行される


func first(where:)

func contains()

func index(where:)

var isEmpty


全て実行される

// 通常の配列などに変換する

let array = Array(lazySequence)

func reduce(_:,_:)

func sorted()
func sorted(by:)

func joined(separator:)


未完成

未完成なので、ちょっとつづ追加していきたい。