評価戦略

遅延評価

Haskell は遅延評価 (lazy evaluation) と呼ばれる評価方式を基本とします。 遅延評価は非正格評価 (non-strict evaluation) の一種であり,次のような評価方式を言います。

ここで,if 式をラッピングした次のような関数 if' を考えてみます。

if' p x y = if p then x else y

if' (1 < 2) (3 * 4) (5 * 6) の評価は,正格と非正格で次のように異なります。

-- 正格評価
if' (1 < 2) (3 * 4) (5 * 6)
= if' True 12 30                   -- 引数を全て評価する
= if True then 12 else 30
= 12

-- 非正格評価
if' (1 < 2) (3 * 4) (5 * 6)
= if 1 < 2 then 3 * 4 else 5 * 6   -- 引数を評価しないまま関数定義を展開
= if True then 3 * 4 else 5 * 6
= 3 * 4                            -- 5 * 6 は評価されない
= 12

Haskell で無限リストに関数を適用してもプログラムが終了することは,遅延評価によって説明できます。

take 3 [1..]
= 1 : take 2 [2..]
= 1 : 2 : take 1 [3..]
= 1 : 2 : 3 : take 0 [4..]
= 1 : 2 : 3 : []
= [1, 2, 3]

正格評価

Haskell で正格評価を行うには,関数 seq :: a -> b -> b を用います。 式 x `seq` y は,式 x を評価した後に式 y を評価します。

次のプログラムは,if 式をラッピングした関数 if'(非正格)と if''(正格)を定義したものです。

-- if1.hs

-- 非正格
if'  p x y = if p then x else y

-- 正格
if'' p x y = p `seq` x `seq` y `seq`  -- p, x, y を評価
             if p then x else y

main = do print $ if'  True 1 (1 `div` 0)  -- 出力: 1
          print $ if'' True 1 (1 `div` 0)  -- 例外発生

また,右辺を即時評価する演算子として $! 演算子が定義されています。

($!) :: (a -> b) -> a -> b
f $! x = x `seq` f x

$! 演算子は $ 演算子のバリアントで,次のように使われます。

square x = x * x

-- $ 演算子を使用
square $ square $ 1 + 2
= square $ square (1 + 2)
= square (square (1 + 2))
= (square (1 + 2)) * (square (1 + 2))
= ((1 + 2) * (1 + 2)) * ((1 + 2) * (1 + 2))
= 81

-- $! 演算子を使用
square $! square $! 1 + 2
= square $! square 3
= square $! 3 * 3
= square $! 9
= 9 * 9
= 81

正格性フラグ

Haskell ではデータ構造も遅延性を持ち,フィールドは必要になるまで評価されません。 フィールドを正格評価するには,data 宣言において正格性フラグ ! を用います。

次のプログラムは,タプルを模した型 Tuple(非正格)と Tuple'(正格)を定義したものです。

-- tuple1.hs

data Tuple  a b = T  a  b   -- 非正格
data Tuple' a b = T' !a !b  -- 正格

first  (T  x y) = x
first' (T' x y) = x

main = do print $ first  $ T  'a' (1 `div` 0)  -- 出力: 'a'
          print $ first' $ T' 'a' (1 `div` 0)  -- 例外発生
目次に戻る