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

smart constructors その4

前へ 目次 次へ

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

引き続き、Distribution.Simple.Command の OptDescr smart constructors セクションを見てみる。最初に今回の記事を ghci で試すための準備をする。

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

noArg

noArg のソースコードは次のようになる。

noArg :: (Eq b, Monoid b) => b -> MkOptDescr (a -> b) (b -> a -> a) a
noArg flag sf lf d = choiceOpt [(flag, (sf,lf), d)] sf lf d

noArg 関数は内部で choiceOpt 関数を使っている。choiceOpt 関数の定義は次のようになっている。フラグと OptFlags (ショートフラグのリストとロングフラグのリストのペア) とオプションの説明文からなるタプルのリストを引数とし、MkOptDescr 型の関数を返す。

-- | create a Choice option
choiceOpt :: Eq b => [(b,OptFlags,Description)] -> MkOptDescr (a -> b) (b -> a -> a) a
choiceOpt aa_ff _sf _lf _d get set  = ChoiceOpt alts
    where alts = [(d,flags, set alt, (==alt) . get) | (alt,flags,d) <- aa_ff]

choiceOpt の引数の (flag, (sf,lf), d) というタプルにはフラグ(flag)、ショートオプションのリスト(sf)とロングオプションのリスト(lf)のペア、それに、オプションの説明(d) が収められている。さらに choiceOpt 関数はこのタプル単体ではなくそのリストを引数にしている。

choiceOpt 関数はこの (flag, (sf,lf), d) のタプルから (d, flags, set alt, (==alt) . get) というタプルを作り出す。次にしめす ChoiceOpt コンストラクタの型を見ると、d は オプションの説明、flags は (sf,lf) のペア, set alt はフラグに値を設定する関数、(==alt) . get はフラグの内容を表示する関数であることが分かる。

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

ChoiceOpt について解読するためには実例を使うのが一番良い。この記事のシリーズの以前の記事で Distribution.Simple.Setup の globalCommand には ChoiceOpt の実例が含まれているのが分かっている。また、その取り出し方は「CommnadUI の読み方」の記事で調べたが、ここで復習してみる。ghci に冒頭で述べた処理をしておくと以下のコードを実行することができる。

Distribution.Simple.Setup の globalCommand のすべての情報は CommandUI 型の globalCommand に保存されている。ChoiceOpt は globalCommand の commandOptions フィールドの中に収められている。まず commandOptions フィールドのデータの型を見てみる。

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

これは commandOptions フィールドの値が ShowOrParseArgs の値で切り替えられる OptionField のリストであることが分かる。そこで ShowArgs の場合の値の型を見てみる。データが Show クラスのインスタンスではない場合 ghci で内容の表示ができないが、:t コマンドを使うとデータの型は見ることができる。

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

OptionField 型のリストを取り出せた。OptionField 型の構成は次のようになっている。

data OptionField a
  = OptionField {optionName :: Name, optionDescr :: [OptDescr a]}
      -- Defined in Distribution.Simple.Command'

OptionField のリストにどんなオプションが入っているか optionName で調べてみた。

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

そこで "version" オプションの optionDescr を取り出し、foo にバインドした。

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

foo には何個の要素が含まれているか検査してみた。

ghci> length foo
1

どうやら1個しかないようなのでそれを取り出して、bar にバインドした。

ghci> let bar = head foo
ghci> :t bar
bar :: OptDescr GlobalFlags

これでついに ChoiceOptの実例を取り出せた。

しかし、これで終わりではない。ChoiceOpt のパラメータは次のように (Description, OptFlags, a -> a, a -> Bool) タプルのリストになっているからだ。

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

そこでパターンマッチで bar のパラメータを取り出し baz にバインドした。

ghci> let baz = p where (ChoiceOpt p) = bar
ghci> :t baz
baz
  :: [(Description,
      OptFlags,
      GlobalFlags -> GlobalFlags,
      GlobalFlags -> Bool)]

baz の要素数を調べてみると 1 だったので、要素のタプルは1個だけだ。

ghci> length baz
1

そこで bas の1個だけのタプルから、タプルの要素 d, flags, set, get をとりだした。

ghci> let (d, flags, set, get) = head baz
ghci> d
"Print version information"
ghci> flags
("V",["version"])
ghci> :t set
set :: GlobalFlags -> GlobalFlags
ghci> :t get
get :: GlobalFlags -> Bool

get を試してみた。

ghci> get defaultGlobalFlags
False

これは defaultGlobalFlags のパラメータの最初のフラグの値を取り出している。

ghci> :t GlobalFlags
GlobalFlags :: Flag Bool -> Flag Bool -> GlobalFlags

set も試してみた。

ghci> get $ set defaultGlobalFlags
True

set 関数で Flag True にセットしたフラグの値を get で確認している。

ChoiceOpt [(d, flags, set, get)] の説明文の d や、ショートオプションとロングオプションのペア flags :: (sf,lf) については特に問題ないので、set や get の関数を定義できればマニュアルで ChoiceOpt 型のデータが作成できる。

まず get を考える。GlobalFlags の構成は次のようになうから、

ghci> :info GlobalFlags
data GlobalFlags
  = GlobalFlags {globalVersion :: Flag Bool,
        globalNumericVersion :: Flag Bool}
      -- Defined in `Distribution.Simple.Setup'
instance Monoid GlobalFlags
  -- Defined in `Distribution.Simple.Setup'

globalVersion アクセサで defaultGlobalFlags がら globalVersion フィールドのフラグ値が取り出せる。

ghci> globalVersion defaultGlobalFlags
Flag False

しかしながら、get 関数では Flag 型ではなく Bool 型が戻ってきて欲しい。そこで (== Flag True) を使うとうまい具合に上の例でも False が戻ってくる。

ghci> (== Flag True) $ globalVersion defaultGlobalFlags
False

つぎに set 関数の場合だが、globalVersion フィールド名を使うとフラグの値をセットできる。

ghci> globalVersion $ defaultGlobalFlags {globalVersion = Flag True}
Flag True

注: デフォールトのフラグの値も利用しつつ mappend で新しい値と演算したあとでフラグをセットしたい場合もある。元々の値に新しい値をアペンドしたいときなどだ。直接に記述するとコードが長くなりすぎるので、つぎのような mget と mset を定義する。

ghci> let mget = globalVersion
ghci> let mset v = defaultGlobalFlags {globalVersion = v}

この mget, mset を利用してフラグの元の値と新しい値のアペンドを行ってフラグの値を再設定する map 関数を次のように定義することができる。

ghci> let map w = mset $ mget defaultGlobalFlags `mappend` w
ghci> mget $ map (Flag True)
Flag True

上の結果ではフラグの値が単に新しい値に置き換わっただけだが、Flag a の mappend がそういう定義になっているためだ。

ghci> Flag False `mappend` Flag True
Flag True

choiceOpt 関数の定義を再掲するが、上の仕組みを頭において眺めるとコードの意味が分かる。

-- | create a Choice option
choiceOpt :: Eq b => [(b,OptFlags,Description)] -> MkOptDescr (a -> b) (b -> a -> a) a
choiceOpt aa_ff _sf _lf _d get set  = ChoiceOpt alts
    where alts = [(d,flags, set alt, (==alt) . get) | (alt,flags,d) <- aa_ff]

ちなみに globalCommand の version オプションの場合、

d = "Print version information"
flags = (['V'], ["version"])
alt = Flag True
set = (\v flags -> flags { globalVersion = v })
get = globalVersion

のようになる。これらは、globalCommnad の定義をする際の option 関数の引数として使われている。Distribution.Simple.Setup の globalCommand の定義の該当部分を再掲すると次のようになる。

    commandOptions = \_ ->
      [option ['V'] ["version"]
        "Print version information"
        globalVersion (\v flags -> flags { globalVersion = v })
        trueArg
      ,option [] ["numeric-version"]
        "Print just the version number"
        globalNumericVersion (\v flags -> flags {globalNumericVersion = v })
        trueArg
      ]

最後に choiceOpt 関数を使って ChoiceOpt コンストラクタ型のデータをマニュアルで作り、この記事を終わる。

ghci> let hoge = choiceOpt [(Flag True, (['V'],["version"]), "Print version information")] ['V'] ["version"] "Pring version information" globalVersion (\v flags -> flags {globalVersion = v})

ghci> :t hoge
hoge :: OptDescr GlobalFlags

ghci> let (_, _, hset, hget) = head h where (ChoiceOpt h) = hoge
ghci> hget $ hset defaultGlobalFlags
True
by tnomura9 | 2014-02-09 17:24 | Haskell | Comments(0)
<< IT断食 smart construct... >>