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の中から処理の流れ(制御)を外側に取り出すことも可能です。
このようにすることで、副作用を排除しながら、モジュール化されたプログラムを書くことが可能です。これは、おそらく、もっと汎用的なパターンにまとめられるのではないかと思います。