Concurrent Clean : 再帰を繰り返しに : wget
昨日の話([id:lethevert:20060413:p1])は、以前にwgetのようなプログラムをCleanでどのように書くかという話([id:lethevert:20051126:p1]、[id:lethevert:20051124:p1])をした時の話につながります。
実際には動かないCleanプログラムの例として、次のようなものを挙げていました。
// Webはリンクでつながった再帰的な構造をしたデータとして表現される ::Web = Web WebPage [Web] getWeb page = WebPage page (map getWeb pages) where pages = getLinkedPages page // wgetの本体 wget firstpage = saveWeb 0 www where // Webのデータを、無限に再帰的なデータとして、先に宣言しておく www = getWeb firstpage // 宣言されたデータを使って、wgetの処理を書く。 saveWeb MAXDEPTH _ = done saveWeb depth (Web page links) # printWebPage page = map (saveWeb (depth+1)) links
これは、昨日と同型の問題なので、同じような発想が適用できます。
::Web = Web WebPage [(*Socket -> *(Web, *Socket))] getWeb :: Url *Socket -> (Web, *Socket) getWeb url socket # (page, socket) = browse url socket urls = getLinkedUrls page = (Web page (map getWeb urls), socket)
という形で、先にデータの取得部分を用意して、
saveWeb :: Int Web (*Socket, *File) -> (*Socket, *File) saveWeb MAXDEPTH (socket, file) = (socket, file) saveWeb depth (Web page links) (socket, file) # file = printWebPage page file (linkedWebs, socket) = mapU links socket = reelU (map (saveWeb (depth+1)) linkedWebs) (socket, file)
というようにsaveWebの処理を書きます。ここでは、saveWebの部分は再帰的に書きましたが、これも同じような形に展開して、saveWebの中から処理の流れ(制御)を外側に取り出すことも可能です。
このようにすることで、副作用を排除しながら、モジュール化されたプログラムを書くことが可能です。これは、おそらく、もっと汎用的なパターンにまとめられるのではないかと思います。