IArrayの怠け度合い

今日はちょっとしたメモ…

リストのレイジーな振舞い方は割りとはっきりとイメージできるのですが、ちょっと遊びでIArrayを使っていて遅延評価がどういった形で動いているのかが気になったので、ちょっと実験してみました。

IArray、つまりImmutableなArrayを作る方法はいくつかあるのですが、基本は配列に変換したいデータをリストとして渡す形になります。僕が気になったのは、リストはいつどどういった形で評価されるのかということですね…たとえば、

getContents :: IO String

などは、unsafeを使ってそれぞれの文字が必要になったときにIOを行うようになっています。getContentsで取得した文字列を配列に変換するとして、ファイルIOはいったいどの時点で起きるのでしょう?

ということで書いてみた実験コードはこんな感じ:

module Main where

import Data.Array.IArray
import Debug.Trace

data SqueakyList a = Sq a (SqueakyList a) | Nil

toSql :: [a] -> SqueakyList a
toSql [ ] = Nil
toSql (a:as) = Sq a $ toSql as

toLst :: SqueakyList a -> [a]
toLst Nil = [ ]
toLst (Sq a as) = trace "list" $ a : (toLst as)

squeak a = trace ("element" ++ (show a) ) a

str = "test"
strSq = map (squeak) str

print3rd :: Array Int Char -> IO ()
print3rd ar = trace "!2" $ print (ar ! 2)

ar :: Array Int Char
ar = arit $ toLst $ toSql $ map (squeak) str

arit :: [a] -> Array Int a
arit as = listArray (0, 3) as

main = do
	putStrLn "Test1"
	print3rd $ arit $ toLst $ toSql strSq
	putStrLn "Test2"
	print3rd $ arit $ toLst $ toSql strSq
	putStrLn "Test3"
	print $ take 2 $ assocs ar
	putStrLn "Test4"
	print $ assocs $ ar

リストの各要素が展開されていくのを見るためにSqueakyListという型を定義しました。これはリストの要素が展開されるたびにトレースを出すようになってます。ここはもっとシンプルなやり方もありそうですね…

そして、このコードの出力はこうなりました:

Test1
!2
list
list
list
list
element's'
's'
Test2
!2
list
list
list
list
's'
Test3
list
list
list
list
element't'
element'e'
[(0,'t'),(1,'e')]
Test4
element's'
element't'
[(0,'t'),(1,'e'),(2,'s'),(3,'t')]

さて、何がわかったかというと:
1.配列の生成に渡したリストは配列の要素への最初のアクセスが起きた時点で配列の生成に使われる部分はすべて展開される。ということで、getContentsの結果をそのまま配列の生成に使うと、必要なIOはすべて配列の要素に始めてアクセスする時点で起きるようです。これはTest1のトレースが!2の後にlistが4回立て続けに起きていることでわかりますね…

さらに、ほかにもわかったことがあります。
2.どうやらリストの各要素に格納されている値もボックス型になっているようで、値が評価されるのはどうやら配列の中のその要素がアクセスされた時点のようです。Test1では3文字目の's'を取り出すのにelementトレースが一つ出てますね…でも、Test2のほうではメモ化が効いているようでelementトレースが出ていません。ということで、メモ化はstrSqの内部で起きているということがいえそうですね。

3.さらにTest3とTest4を見てもメモ化の作用が見て取れますね。

ちなみにリストは今回のSqueakyListでもわかるようにHaskellの言語の範疇でで簡単に作ることができるのですが、配列のほうはどうもそうはいかないようですね。IArrayのコードをたどっていくと最終的にはコンパイラなのか、ランタイムなのかわかんないんですが、unsafeな配列生成内部関数が呼び出されているのがわかります。リンクリストよりも配列のほうがインプリメントが難しい言語というのも面白いですね…

きょうはこのへんで。
ではでは。