Haskellのお勉強 その6
遅延評価
Haskellの大きな特徴として、遅延評価を基本原則としているというのがある。遅延評価とは、評価しなければならない値が存在するとき、実際の計算を値が必要になるまで行わないことを言う。
つまり、遅延評価はトヨタ式の、必要なときに必要なものを必要なだけ作るってゆー
ジャストインタイムのようなものをイメージすると理解しやすいかも。
Haskellの式の評価は、最外簡約でグラフ簡約
関数の適用を定義で置き換えることを簡約という。簡約には最内簡約と最外簡約がある。
以下のような関数があった場合
square n = n * n
■最内簡約
{-C#やJavaの評価のされ方-} square (2 + 3) → square 5 → 5 * 5 → 25
■最外簡約
{-Haskellの評価のされ方-} square (2 + 3) → (2 + 3) * (2 + 3) → 5 * 5 → 25
上記の最外簡約の(2 + 3)が2つに増えていることに着目したとき、
(2 + 3)は2箇所別々に簡約されるだろうか。それとも
引数としては1つであるから、簡約は1度のみになるのだろうか。
Haskellの評価方法は、後者となる。つまり、1つの引数は1度だけ評価される。
このように式を共有しながら簡約する方法をグラフ簡約と言う。
この最外簡約とグラフ簡約の二本柱が、Haskellの遅延評価の礎となっている。
ifを関数として書く
ふつける本に載ってるまんまだが、ifを関数として表現すると下記のようになる。main = do myIf (True) (putStrLn "then") (putStrLn "else") myIf :: Bool -> a -> a -> a myIf True t e = t myIf False t e = e
この独自に定義したmyIf関数を使ったプログラムは「"then"」だけを表示する。
したがって、else側のアクションは評価されず遅延されたままとなることがわかる。
このように遅延評価には、評価に必要な式だけが評価されるという特徴がある。
データ構造の遅延評価
Haskellではデータ構造も遅延評価される。つまり、データ構造も必要最小限の部分のみが作成される。
例えばリストであれば、実際にプログラムで利用される要素のみが作成される。
つまり、あるリストの第1要素しか使わないのであれば、そのリストの第2要素以降は作成されない。
例えば以下のような場合、一見lines csによって
標準入力すべての要素に対してリスト分割されそうに見えるが。
take nがあり、nは10であるから、この場合、lines csでは10行分のリストのみが取得される。
main = do cs <- getContents putStr $ firstNLines 10 cs firstNLines n cs = unlines $ take n $ lines cs
また、下記の場合、リストを末尾まで評価しなければlength関数の戻り値が得られないので
リスト自体はすべて評価されることとなるが、リストの要素は必要ないので、
(1 + 1)、(2 + 2)、(3 + 3)のそれぞれの評価は遅延されたまま、length関数は3を得ることができる。
length [(1 + 1),(2 + 2),(3 + 3)]
遅延評価のメリット/デメリット
■遅延評価のメリット
・不必要な評価を減らすことができる
・無限の長さのリストが扱うことができる
(終わりの見えない、あるいは終わりがないデータをリストとして扱うことができる)
・インターフェイスが統一できる(すべての操作は「リストに対する処理」とみなせる)
■遅延評価のデメリット
・想定する順番で操作を実行するのが難しい
・ソースコードの可読性が低くなりがち
・デバッグしにくい(スタックトレースが存在しない)