遅延評価は無限を取り扱える
Haskellでは遅延評価という仕組みがある。この仕組みの中では、関数は引数にとるデータについて必要な分だけを使うことになる。
例えば、
take 3 [0..] -- [0,1,2]
とすれば、0から無限に続く正の整数というまとまりから、先頭の3つの要素を取り出すことができる。これはとても便利な仕組みだ。なぜなら、 まとまりそのものと、それをいくつ取り出すかという条件を独立させることができるからだ。これにより、プログラマは対象となるまとまりそのものと、その停止条件を別個に設計できるようになる。
filter even [0..] -- 偶数
filter odd [0..] -- 奇数
fibs -- フィボナッチ数列
fibs = 1 : 1: zipWith (+) fibs (tail fibs)
なお、先の例をJavaScriptで書くとこのようになるだろう。
function take(n) {
var arr = [];
var i;
for (i = 0; i < n; i++) {
arr.push(i);
}
return arr;
}
後者は、n個の整数といった有限のまとまりしか扱っていないことがわかる。これを切り離して無限にしてしまったら、プログラムが停止してしまう。モジュール性の観点から言えば、まとまりそのものと、それをいくつ取り出すかという条件を分けて表現できた方が嬉しいことが多い(そんなことを前に書いた:モジュール化について考えてみたときのまとめ)
遅延評価とIO
そんななか、何か他に良い題材はないかと考えていたところ、いつも参考にさせていただいてるブログの遅延評価とIOという記事にとても興味を引くことが書いてあった。
つまり IO に関わる関数さえも、遅延評価というノリのおかげで、完全に独立な部品となるのである。
なるほど、IOに関わる関数さえもなんらかのまとまりとして扱うことができる。あとは独立して停止条件を設計し、つなぎ合わせれば無駄な処理なく必要な分だけ実行できるようになるということだ。WebアプリケーションのようにIOモナドの実装が多くなりがちなケースでは特に、この実装方針を応用して、よりリーダブルなコードにしたい!と思った。
具体的にはHTTPから受け取るデータとか、ロードするデータ(ファイルまたはデータベース)に対する操作を、なんらかのまとまりとして捉える、ということになるだろう。
サンプル
とても残念だが、実践的なアイデアは浮かばなかった。代わりに、次のようなまとまりと停止条件を考えてみた。
モジュール | サンプル仕様 |
---|---|
まとまり | ランダムな値を生成し、標準出力に表示するIOアクションが無限に続くリスト |
停止条件 | 生成された値が、実行時に引数として渡す任意の整数にマッチするまで |
次のようなプログラムになった。
import System.Random
import System.Environment
import Control.Monad
-- 0から30までのInt型の値をランダムに選び、標準出力に表示したあとに返す
randInt :: IO Int
randInt = do
gen <- newStdGen
let ret = fst $ randomR (0, 30) gen
putStrLn $ show ret
return ret
-- IO Int のリストをとり、指定した数字にマッチするまでIOアクションを実行する
searchNumber :: Int -> [IO Int] -> IO ()
searchNumber _ [] = putStrLn "Nothing"
searchNumber x (im:ims) = do
y <- im -- ここでIO アクションが実行される
unless (x == y) $ searchNumber x ims
main :: IO ()
main = do
args <- getArgs
searchNumber (read $ head args) (repeat randInt)
$ runhaskell lazyList.hs 10
6
18
24
10
無限に続くIOアクションのリストを引数にとるものの、引数に入れた数字とマッチすれば即座に停止していることがわかる。
また、randInt
、searchNumber
それそれが独立しているので、別の関数やまとまりとつなぎ合わせることが可能になっている。
終わりに
最近、Node.js
も並行して学習しているのだが、ここでもストリームという概念が登場していて、利用する目的が似ている。まとまりとそれをどう取り扱うか、ということについてきちんと知識と勘所を正しく身に付けることが、次世代のプログラミングスキルとしてとっても重要な気がしている。