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

CommnadUI の読み方

前へ 目次 次へ

参照: cabal-instsll 関連のリンク Distribution.Simple.Commnad Distribution.Simple.Setup

今回からは、Distribution.Simple.Command で定義されている代数的データ型をひとつひとつ眺めていく。

最初は CommnadUI 型だ。CommnadUI 型には Hddock 文書によれば、Cabal の様々なサブコマンドの名前、説明、フラグのセットがまとめられている。CommnadUI のコンストラクタは CommnadUI 1つだが、次のようなフィールド名のパラメータを持っている。

CommnadUI
    commandName :: String
    commandSynopsis :: String
    commandUsage :: String -> String
    commandDescription :: Maybe (String -> String)
    commandDefaultFlags :: flags
    commandOptions :: ShowParseArgs -> [OptionField flags]

各フィールドの意味は以前の記事で調べたので、ここでは CommandUI 型のデータから各フィールドの値を取り出す方法を考えてみる。幸い Distribution.Simple.Setup には globalCommand という CommnadUI 型のデータが定義されているので、それを材料に ghci でテストしてみる事ができる。

まずは、Distribution.Simple.Command と Distribution.Simple.Setup と Data.Maybe をインポートする。

Prelude> import Distribution.Simple.Command
Prelude Distribution.Simple.Command> import Distribution.Simple.Setup
Prelude Distribution.Simple.Command Distribution.Simple.Setup> import Data.Maybe
Prelude Distribution.Simple.Command Distribution.Simple.Setup Data.Maybe> :set prompt "ghci> "
ghci>

commandNamecommandSynopsis はフィールド名と同じアクセサで簡単に取り出せる。

ghci> commandName globalCommand
""
ghci> commandSynopsis globalCommand
""
commandUsage フィールドのデータは String -> String 型の関数だから、文字列の引数が必要だ。

ghci> (commandUsage globalCommand) "cabal"
"This Setup program uses the Haskell Cabal Infrastructure.\nSee http://www.haskell.org/cabal/ for more information.\n"

これには putStr を使った方がいいかもしれない。

ghci> putStr $ (commandUsage globalCommand) "cabal"
This Setup program uses the Haskell Cabal Infrastructure.
See http://www.haskell.org/cabal/ for more information.

commandDescription フィールドのデータは Maybe (String -> String) 型を取り出すためには、Maybe 型のコンテナからデータを取り出さなければならない。今回は Data.Maybe モジュールの fromJust 関数を使う。

ghci> putStr $ (fromJust $ commandDescription globalCommand) "cabal"
For more information about a command use
cabal COMMAND --help

Typical steps for installing Cabal packages:
cabal configure
cabal build
cabal install

commandDefaultFlags フィールドのデータは flags という型変数になっている。Distribution.Simple.Setup モジュールのフラグ型が分からないので :t コマンドで調べてみる。

ghci> :t commandDefaultFlags globalCommand
commandDefaultFlags globalCommand :: GlobalFlags

GlobalFlags は Distribution.Simple.Setup で次のように定義されている。

data GlobalFlags = GlobalFlags {
    globalVersion    :: Flag Bool,
    globalNumericVersion :: Flag Bool
  }

GlobalFlags のフィールドにもアクセスしてみる。

ghci> globalVersion $ commandDefaultFlags globalCommand
Flag False

Flag 型も Distribution.Simple.Setup.hs で定義されている。

data Flag a = Flag a | NoFlag deriving (Show, Read, Eq)

commandDefaultFlags のフラグのセットは、commandsRun がコマンド・ラインオプションを解析してフラグを設定するときのデフォールトの値になる。globalCommand の commandDefaultFlags の値は、

{globalVersion = Flag False, globalNumericVersion = Flag False}

だ。

commandOptions フィールドの値は複雑で Show クラスのインスタンスでもないので、ghci のコマンド・ラインでは表示できない。このような場合は :t コマンドでデータ型を表示させるとよい。

ghci> :t commandOptions globalCommand
commandOptions globalCommand
:: ShowOrParseArgs -> [OptionField GlobalFlags]

ShowOrParseArgs は Distribution.Simple.Command モジュールで定義されている。

data ShowOrParseArgs = ShowArgs | ParseArgs

ghci> :t commandOptions globalCommand ShowArgs
commandOptions globalCommand ShowArgs :: [OptionField GlobalFlags]

OptionField 型は2つのフィールドを持っている。

OptionField
    optionName :: Name
    optionDescr :: [Descr a]

optionName の方を試してみる。Name は単なる String の別名なので ghci で表示できる。

ghci> optionName $ head $ commandOptions globalCommand ShowArgs
"version"

commandOptions フィールドのリストの optionName を全て取り出してみた。

ghci> map optionName $ commandOptions globalCommand ShowArgs
["version","numeric-version"]

"version" オプションの optionDescr フィールドの値を取り出してみた。

ghci> :t optionDescr $ head $ commandOptions globalCommand ShowArgs
optionDescr $ head $ commandOptions globalCommand ShowArgs
:: [OptDescr GlobalFlags]

[OptDessr GlobalFlags] の最初の要素を取り出し foo に束縛した。

ghci> let foo = head $ optionDescr $ head $ commandOptions globalCommand ShowArgs
ghci> :t foo
foo :: OptDescr GlobalFlags

OptDescr a 型の定義も Distribution.Simple.Command にあるが、コンストラクタが4つあり、フィールドの構成も複雑だ。

OptDescr a
    ReqArg Description OptFlags ArgPlaceHolder (ReadE (a -> a)) (a -> [String])
    OptArg Description OptFlags ArgPlaceHolder (ReadE (a -> a)) (a -> a) (a -> [Maybe String])
    ChoiceOpt [(Description, OptFlags, a -> a, a -> Bool)]
    BoolOpt Description OptFlags OptFlags (Bool -> a -> a) (a -> Maybe Bool)

foo のコンストラクタがどれに一致するのかわからず試行錯誤したが、ChoiceOpt のようだ。

ghci> let ChoiceOpt a = foo
ghci> a

a の型を調べてみた。

ghci> :t a
a :: [(Description,
       OptFlags,
       GlobalFlags -> GlobalFlags,
       GlobalFlags -> Bool)]

a の先頭の要素を取り出した。

ghci> :t head a
head a
:: (Description,
OptFlags,
GlobalFlags -> GlobalFlags,
GlobalFlags -> Bool)

head a のパラメータをパターンマッチで取り出してそれぞれの内容を確認した。

ghci> let (w,x,y,z) = head a
ghci> w
"Print version information"
ghci> x
("V",["version"])
ghci> :t y
y :: GlobalFlags -> GlobalFlags
ghci> :t z
z :: GlobalFlags -> Bool

こうしてみると、CommandUI 抽象が非常に複雑な情報を1つの値として保持していることがわかる。また、ここでやった値の取り出し方は、プログラムで行う方法と同じなので、プログラムを読むのが楽になるだろう。

とりだしたパラメータの y は GlobalFlags -> GlobalFlags の形の関数なのでフラグセットの操作をするのではないかと思われる。それでデフォールトのフラグセット commandDefaultFlags に関数適用したらどうなるかが気になってきた。

まずデフォールトのフラグセットを取り出して bar にバインドした。

ghci> let bar = commandDefaultFlags globalCommand

次にフラグセットの内容をリスト表示に変換する関数 flagsToList を定義した。

ghci> let flagsToList flag = [globalVersion flag, globalNumericVersion flag]

flagsToList を使って bar の内容を見ると次のようになる。

ghci> flagsToList bar
[Flag False,Flag False]

bar に y を適用してみた。

ghci> flagsToList $ y bar
[Flag True,Flag False]

globalVersion のフラグが立っている。

z :: GlobalFlags -> Bool も試してみた。

ghci> z bar
False
ghci> z $ y bar
True

どうやら z は GlobalFlags の globalVersion フラグが立っているかどうかを判定しているようだ。

CommandUI の内容を手動で操作できると、それぞれのパラメータの意味が体感的に理解できるので、プログラムのイメージが作りやすい。
by tnomura9 | 2014-01-06 23:41 | Haskell | Comments(0)
<< commandShowOptions The Haskell Cab... >>