佐々木屋

技術的なことから趣味まで色々書きます

遅延評価と先行評価

式や配列などの走査で必要な要素を必要な時だけ評価するような挙動を遅延評価と言います。イテレーターは遅延評価される処理の典型です。LINQなどでも遅延評価があります。

例えば、イテレーターでも出てきた以下の配列処理を考えます。

private List<string> Naiyo() {
    List<string> res = new List<string>();

    res.Add("hoge");
    Console.WriteLine("hoge終了");

    res.Add("piyo");
    Console.WriteLine("piyo終了");

    res.Add("fuga");
    Console.WriteLine("fuga終了");
    
    return res;
}

foreach (var n in Naiyo()) {
    Console.WriteLine(n);
}
Private Function Naiyo() As List(Of String)
    Dim res As New List(Of String)

    res.Add("hoge")
    Console.WriteLine("hoge終了")

    res.Add("piyo")
    Console.WriteLine("piyo終了")

    res.Add("fuga")
    Console.WriteLine("fuga終了")

    Return res
End Function

For Each n As String In Naiyo()
    Console.WriteLine(n)
Next

これを実行すると、

hoge終了
piyo終了
fuga終了
hoge
piyo
fuga

つまり、Naiyoメソッドが先に全て評価(先行評価)され、その後に配列が処理されているのが分かると思います。

これをイテレーターを使って遅延評価にするとどうなるでしょうか。

private IEnumerable<string> INaiyo() {
    yield return "hoge";
    Console.WriteLine("hoge終了");

    yield return "piyo";
    Console.WriteLine("piyo終了");

    yield return "fuga";
    Console.WriteLine("fuga終了");
}
Private Iterator Function INaiyo() As IEnumerable(Of String)
    Yield "hoge"
    Console.WriteLine("hoge終了")

    Yield "piyo"
    Console.WriteLine("piyo終了")

    Yield "fuga"
    Console.WriteLine("fuga終了")
End Function
hoge
hoge終了
piyo
piyo終了
fuga
fuga終了

どうでしょうか。結果が変わりましたね。

INaiyoメソッドはyield returnまで評価され、配列処理が一つ終わるとINaiyoメソッドの続きが実行されます。そしてまた、yield returnまで実行される、という感じで順番に評価されていることが分かると思います。

まとめると、先行評価である通常のメソッドはメソッド全体を評価して結果を返しますが、遅延評価のイテレーターの場合はyieldまで到達したら処理を中断し呼び出し元の処理に戻します。つまり、遅延評価というのは必要な時が来るまで保留しておき、必要な分しか評価しない、ということになります。

遅延評価の話はLINQを利用した際も同様となります。