あどけない話

インターネットに関する技術的な話など

遅延評価とIO

僕は今、プログラマーとしての幸福感に満たされている。遅延評価を習得できたと思えるからだ。

遅延評価

なぜ関数プログラミングは重要かには、遅延評価の利点を以下のように説明している。

停止条件はループの本体とは切離すことができ、強力なモジュール化が可能となる。

例として載っている「ニュートン-ラプソン法による平方根」は、若干難しいので、簡単な別例を示そう。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 レコードを引く
    • 左から右へ合致するか調べる
      • 合致したら終了
    • 検索する SPF レコードがなくなったら終了

遅延評価を知っているなら、

  • 合致するか調べる (合致したらそこで終わり)
  • SPF レコードを全部引く

という完全に独立した部品を作り、遅延評価でつなぎ合わせる。

遅延評価のおかげで、DNS の無駄な検索は起こらない。

数日前に、遅延評価を使えば SPF を処理するコードが簡潔になるのではないかというアイディアを思いついた。実装はかなり難しかったが、できあがったコードは簡潔であり、無駄な検索もしない。

ようやく遅延評価の真の力を自分のものにできた感じだ。