僕は今、プログラマーとしての幸福感に満たされている。遅延評価を習得できたと思えるからだ。
遅延評価
なぜ関数プログラミングは重要かには、遅延評価の利点を以下のように説明している。
停止条件はループの本体とは切離すことができ、強力なモジュール化が可能となる。
例として載っている「ニュートン-ラプソン法による平方根」は、若干難しいので、簡単な別例を示そう。Haskell には、第一引数の数だけ、第二引数を繰り返す関数 replicate がある。
> replicate 3 'a' → "aaa"
これを普通に実装するとこうなる。
replicate 0 c = [] replicate n c = c : replicate (n-1) c
Haskell 以外で実装する場合、きっとループを使うだろう。ただ、ここでは再帰かループかは問題ではない。
問題は、「結果を作る仕事」と「終了条件を判断する仕事」を同時にやっていることだ。遅延評価を活かして replicate を作るとこうなる。
replicate n c = take n (repeat c)
repeat は引数の無限リストを作る関数で、take はリストの先頭から要素を n 個を取り出す関数だ。遅延評価のおかげで、終了条件を判断する仕事は不要となり、take と repeat は完全に独立した部品として存在できることが分かる。
遅延評価と IO
Haskell では、IO もファーストクラスだ。これは、IO をリストに格納できることを意味する。表示はできないが、以下の式は正しく、IO の無限リストを作る。
> repeat (putStrLn "Hello")
リストに格納された IO は命令書であり、リストの中にあるかぎり何もしない。取り出されたときに初めて実行される。そのため、次のように replicate と同等のことができる。
> sequence_ (take 3 (repeat (putStrLn "Hello"))) Hello Hello Hello
つまり IO に関わる関数さえも、遅延評価というノリのおかげで、完全に独立な部品となるのである。
SPF と遅延評価
ここ数日間で、メールの受信側で Sender Policy Framework (SPF) を使って、メールのドメインを認証する Haskell のコードを書いた。
SPF レコードを得るには、DNS を検索するために IO が発生する。あるドメインの SPF レコードが次のようになっていたとしよう。
v=spf1 +ip4:192.0.2.1 +ip4:192.0.2.2 redirect=example.jp
SPF の受信側の認証は、SMTP コネクションの始点 IP アドレスが、この書式で表される IP アドレスに合致するかを確かめる。書式は左から右に評価して行く。合致すればそこで評価が終わる。
この書式の場合、最後まで合致しないと、今度は example.jp の SPF レコードを検索して、同様の作業をしないといけない。
通常のプログラマーなら、上記の手順をそのままプログラムにするだろう。つまり、こんな風にだ。
遅延評価を知っているなら、
- 合致するか調べる (合致したらそこで終わり)
- SPF レコードを全部引く
という完全に独立した部品を作り、遅延評価でつなぎ合わせる。
遅延評価のおかげで、DNS の無駄な検索は起こらない。
数日前に、遅延評価を使えば SPF を処理するコードが簡潔になるのではないかというアイディアを思いついた。実装はかなり難しかったが、できあがったコードは簡潔であり、無駄な検索もしない。
ようやく遅延評価の真の力を自分のものにできた感じだ。