人気ブログランキング | 話題のタグを見る

Haskell Platform Libraries

前へ 目次 次へ

マニュアルばかり読んでいても始まらないので、なにか cabal-install のコード解析に結びつくものはないかと考えた。

cabal-install で import される大量のモジュールは Distribution ではじまるモジュールツリーの Cabal パッケージのライブラリが多い。これらのライブラリのソースを効率よく検索する方法はないかと思って探したらあった。

The Haskell Platform の ホームページの Documentation のページの Libraries だ。

Libraries のメイン・ウィンドウの左側にはツリー形式の Haskell Platform のライブラリのモジュール名が表示されている。また、右側にはそれらのモジュールが含まれるパッケージ名が表示されている。これを見ると Cabal のモジュール群は Distribution 以下のツリーにあり、その右側を見ると Cabal パッケージのバージョンは 1.16.0 であることが分かる。

Cabal のバージョン番号と cabal-install のバージョン番号は同じなので、cabal-install-1.16.0 のソースを入手すれば、cabal-install のコードを解析するのに、この Haddock が利用できることが分かる。

そこで The cabal-install package のページから cabal-install-1.16.0.tar.gz をダウンロードした。圧縮ファイルを解凍してできたディレクトリ cabal-install-1.16.0 に移動して、Main.hs ファイルを読んでみると、main 関数は、

main :: IO ()
main = getArgs >>= mainWorker

となって、コマンドライン引数のリストを mainWorker 関数に引き渡しているだけだ。さいわい mainWorker 関数の定義は main 関数の定義のすぐ下にある。

mainWorker :: [String] -> IO ()
mainWorker ("win32selfupgrade":args) = win32SelfUpgradeAction args
mainWorker args = topHandler $
  ...

mainWorker 関数の詳細に入る前に気になったのは topHandler という関数だ。mainWorker のコードの中でいろいろな処理がされた後の値が最終的に topHandler 関数に渡されている。いったい、topHandler とはどこに記述され、どのような機能があるのだろうか。

Main.hs 関数の冒頭の import 部分を見ると

import Distribution.Simple.Utils
    ( cabalVersion, die, topHandler, intercalate )

となっているので topHandler が Cabal の Distribution.Simple.Utils モジュールの関数である事が分かる。それで先ほどの Libraries ツリーの Distribution.Simple.Utils をクリックするとこのモジュールの Haddock で作成されたこのモジュールの文書のHTML文書が表示される。

topHandler は Distribution.Simple.Utils の logging and errors セクションに記載されており、その型は次のようになる。

topHandler :: IO a -> IO a

これをみると topHandler が IO モナドをアクションを引数にとり、IO モナドのアクションを返す事が分かるが、具体的な動作は分からない。そこで、上の関数の型宣言と同じ行にある Source リンクをクリックすると topHandler のソースを表示できる。その冒頭部分は次のようになる。

topHandler :: IO a -> IO a
topHandler prog = catchIO prog handle
  where
    handle ioe = do

catchIO 関数があるので引数 prog には IO モナドのアクションをとり、アクションにエラーが発生したときに handle 部分が実行されるはずだ。

handle 関数は where 以下で定義されているので、あまり外部の情報を必要としないだろう。エラー処理については topHandler 関数の内部に処理が内在していると思われる。したがって、prog に適当な IO アクションを割り当てる事で topHandler 関数をテストすることができると思われる。

そこで、ghci で topHandler 関数を試してみる事にした。まず必要なモジュールをインポートする。Distribution.Simple.Utils モジュールには topHandler 関数がある。System.IO をインポートしたのは hPutStr を利用してエラーを発生する IO モナドのアクションを作るためだ。

Prelude> import Distribution.Simple.Utils
Prelude Distribution.Simple.Utils> import System.IO

それでは、エラーを発生しないアクションについて topHandler がどのように振る舞うかを見てみよう。putStrLn "hello" はエラーを発生せず、値は IO () だ。このばあい単に putStrLn "hello" が実行される。

Prelude Distribution.Simple.Utils System.IO> topHandler (putStrLn "hello")
hello

そこでエラーを発生するアクションを作ってみる。hPutStr のハンドルに stdin を指定すると stdin には書き込めないのでエラーが発生するはずだ。

Prelude Distribution.Simple.Utils System.IO> hPutStr stdin "hello"
*** Exception: : hPutStr: illegal operation (handle is not open for writing)

このエラーを発生するアクションを topHandler に渡してみると、上のエラーの場合とは違ったフォーマットのエラーメッセージが表示された。

Prelude Distribution.Simple.Utils System.IO> topHandler (hPutStr stdin "hello")
<interactive>: <stdin>: illegal operation
*** Exception: ExitFailure 1

topHandler prog = catchIO prog handle の handle はエラーメッセージをフォーマットして表示する関数だった。そのつもりで topHandler の全ソースを読むと、その動作がどうプログラムされているかがよくわかる。

topHandler :: IO a -> IO a
topHandler prog = catchIO prog handle
  where
    handle ioe = do
      hFlush stdout
      pname <- getProgName
      hPutStr stderr (mesage pname)
      exitWith (ExitFailure 1)
      where
        mesage pname = wrapText (pname ++ ": " ++ file ++ detail)
        file         = case ioeGetFileName ioe of
                         Nothing   -> ""
                         Just path -> path ++ location ++ ": "
#if defined(__HUGS__) || (defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ < 608)
        location     = ""
#else
        location     = case ioeGetLocation ioe of
                         l@(n:_) | n >= '0' && n <= '9' -> ':' : l
                         _                              -> ""
#endif
        detail       = ioeGetErrorString ioe

そんなに長い関数ではない。Haskell のプログラムはモジュール化されているので長大な一本コードというものはあまりないようだ。また、関数型の特性から topHandler 関数のように部分的なコードをテストすることができる。cabal-install にインポートされた大量のモジュールをみると怖じ気がつくが、Haddock を使えばかなり効率的にソースを解析できそうだ。

付録

getProgName :: IO String は System.Environment モジュールの関数。プログラム名を取得する。

Prelude> import System.Environment
Prelude System.Environment> getProgName
"<interactive>"

exitWith :: ExitCode -> IO a 関数はプログラムを終了させ ExitCode を返す。System.Exit モジュールの関数。ExitCode のコンストラクタは次の2つ。

ExitSuccess
ExitFailure Int

Prelude> import System.Exit
Prelude System.Exit> exitWith ExitSuccess
*** Exception: ExitSuccess

目次 次へ
by tnomura9 | 2013-10-23 00:56 | Haskell | Comments(0)
<< The cabal-insta... How to write a... >>