2009年8月1日土曜日

Haskellでレイトレーシング(第2回)〜ループは使わない


いま、Haskellでレイトレーサを書いています。

第1回のブログで、Common LispのコードをHaskellで書くときにポイントとなったところをあげました。

今回は、そのポイントのうち、「1.ループを使わない」について書きます。

ループを使わない


各ピクセルごとに色を計算するために、Common Lispのコードでは以下のように手続き的にループを回しています。
(defun tracer (pathname &optional (res 1))
(with-open-file (p pathname :direction :output)
(format p "P2 ~A ~A 255" (* res 100) (* res 100))
(let ((inc (/ res)))
(do ((y -50 (+ y inc)))
((< (- 50 y) inc))
(do ((x -50 (+ x inc)))
((< (- 50 x) inc))
(print (color-at x y) p)))
一方、Haskellでは手続き的なループは使わないので、このような書き方はしません。

それではどのように書くのかというと、以下のようにリストで表現するのです。
tracer :: FilePath -> [Surface] -> Int -> IO()
tracer pathname world res =
let
cols = [color_at world (pos i) (pos j) | j <- [0..(res*100)-1],
i <- [0..(res*100)-1]]
in
do putPGM pathname (res*100) (res*100) cols
where
putPGM :: FilePath -> Int -> Int -> [Int] -> IO()
putPGM pathname w h cols =
let header = "P2 " ++ show w ++ " " ++ show h ++ " 255\n"
body = foldr (++) "" [(show c) ++ "\n" | c <- cols]
in do writeFile pathname (header ++ body)

pos :: Int -> Double
pos i = (fromIntegral i) / (fromIntegral res) - 50.0
putPGMがpgm画像を出力するアクションで、それにピクセルの色のリストcolsを渡すのです。

このような書き方をすると、「すべてのピクセルをリストにしてから処理するなんて、メモリを無駄に使いすぎるのではないか」と思ってしまいます。しかし、Haskellは遅延評価なので、必要な要素から順々に計算されていくため、リスト全体をメモリに持っておくということは起きません。

このように、リストと遅延評価を使うことで、手続き型のループと同様の処理をHaskellで表現することができるのです。

次回は、「2.型による分岐にはパターンマッチを用いる」について書きます。

■他の記事
Haskellでレイトレーシング(第1回)〜導入
Haskellでレイトレーシング(第2回)〜ループは使わない
Haskellでレイトレーシング(第3回)〜型による分岐にはパターンマッチを用いる
Haskellでレイトレーシング(第4回)〜NilはMaybeモナドで表現する
Haskellでレイトレーシング(第5回)〜タプルを返せる

0 件のコメント: