「ほっ」と。キャンペーン

<   2014年 01月 ( 19 )   > この月の画像一覧

OptionViewer.hs

前へ 目次 次へ

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

ひきつづき Distribution.Simple.Command の Option Descriptions セクションを見てみる。

前回の記事で OptDescr 型の ChoiceOpt コンストラクタと ReqArg コンストラクタの各パラメータの意味が分かったので、OptArg コンストラクタと BoolOpt コンストラクタの各パラメータの意味を調べてみる。

問題はそれらの OptDescr 型のデータの実例をどうして取り出すかだ。そこで、CommnadUI 型の commandOptions フィールドのなかの OptDescr 型をとりだすツールを作ってみた。ツールは OptionViewers.hs という名前にして次のように記述した。

module OptionViewers where

import Distribution.Simple.Command
import Distribution.Simple.Setup
import Distribution.ReadE

viewOptionField commandui = map optionName $ commandOptions commandui ShowArgs

getOptDscr commandui = concatMap optionDescr $ commandOptions commandui ShowArgs

optDescrType opt = case opt of
  ReqArg _ _ _ _ _ -> "ReqArg"
  OptArg _ _ _ _ _ _ -> "OptArg"
  ChoiceOpt _ -> "ChoiceOpt"
  BoolOpt _ _ _ _ _ -> "BoolOpt"

viewOptionField 関数は、CommandUI 型のデータの commandOptions の中の OptionField 型データのリストをよんで、その名前を表示する。

ghci> :l OptionViewers
[1 of 1] Compiling OptionViewers ( OptionViewer.hs, interpreted )
Ok, modules loaded: OptionViewers.

ghci> viewOptionField installCommand
["verbose","builddir","inplace","shell-wrappers","package-db"]

getOptDescr 関数はその OptionField 型のリストから、optionDescr フィールドの OptDescr 型のデータを取り出してリストにする。

ghci> :t getOptDscr installCommand
getOptDscr installCommand :: [OptDescr InstallFlags]

optDescrType 関数は OptDescr 型のデータのコンストラクタがどのタイプかを見る関数だ。

ghci> optDescrType $ head $ getOptDscr installCommand
"OptArg"

したがって、つぎのようにすれば CommandUI 型のデータの中の OptDescr 型のコンストラクタ・タイプの全てを表示させることができる。

ghci> map optDescrType $ getOptDscr installCommand
["OptArg","ReqArg","ChoiceOpt","BoolOpt","ChoiceOpt"]

幸運なことに、installCommand には全てのタイプの OptDescr 型が含まれていた。

getOptDscr 関数は CommandUI 型の commandOptions フィールドに含まれる OptDescr 型を全て取り出すようにプログラムしたので、異なる OptionField 型に含まれる OptDescr 型のデータをまとめてひとつのリストにしてしまっている。それでは、OptDescr 型のデータの内容の特徴が分からないので、installCommnad の commandOptions フィールドの値から順々に抽出をすすめることにする。

まず、 installCommandcommandOptions の値を取り出す。

*OptionViewers> :t commandOptions installCommand
commandOptions installCommand
  :: ShowOrParseArgs -> [OptionField InstallFlags]

これは ShowOrParsArgs 型の値をとり、[OptionField installFlags] 型の値を返す関数だから、ShowOrParseArgs 型の値を与えてやれば Optionfield の値を取り出す事ができる。ShowOrParseArgs の型は次のようになるので、

*OptionViewers> :info ShowOrParseArgs
data ShowOrParseArgs = ShowArgs | ParseArgs
      -- Defined in `Distribution.Simple.Command'

ShowArgs コンストラクタを引数として与えることにする。得られた [Optionfiled InstallFlags] リストは foo にバインドする。

*OptionViewers> let foo = commandOptions installCommand $ ShowArgs
*OptionViewers> :t foo
foo :: [OptionField InstallFlags]

OptionField の構成は次のようになっているから、

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

foo に含まれるオプションフィールドの名前を列記してみる。

*OptionViewers> map optionName foo
["verbose","builddir","inplace","shell-wrappers","package-db"]

そこで "verbose" OptionField をとりだして bar にバインドする。

*OptionViewers> let bar = head foo
*OptionViewers> optionName bar
"verbose"
*OptionViewers> :t bar
bar :: OptionField InstallFlags

どういうコンストラクタのデータが bar の optionDescr に収まっているかを見てみる。

*OptionViewers> map optDescrType $ optionDescr bar
["OptArg"]

これで installCommand の commandOptions フィールドの "verbose" OptionField には 1個だけの OptArg コンストラクタの optDescr 型が含まれていることが分かった。これを取り出して baz にバインドすることにしよう。

*OptionViewers> let baz = head $ optionDescr bar
*OptionViewers> :t baz
baz :: OptDescr InstallFlags
*OptionViewers> optDescrType baz
"OptArg"

OptArg コンストラクタのパラメータは次のようになっている。

*OptionViewers> :t OptArg
OptArg
  :: Description
    -> OptFlags
    -> ArgPlaceHolder
    -> ReadE (a -> a)
    -> (a -> a)
    -> (a -> [Maybe String])
    -> OptDescr a

そこでパターンマッチでパラメータを取り出して p0 .. p1 にバインドする。

*OptionViewers> let (p0,p1,p2,p3,p4,p5) = (q0,q1,q2,q3,q4,q5) where (OptArg q0 q1 q2 q3 q4 q5) = baz

*OptionViewers> p0
"Control verbosity (n is 0--3, default verbosity level is 1)"
*OptionViewers> p1
("v",["verbose"])
*OptionViewers> p2
"n"

p3, p4, p5 は InstallFlags を操作する関数だ。ところで defaultInstallFlags の各フラグは次のようになっている。

*OptionViewers> defaultInstallFlags
InstallFlags {installPackageDB = NoFlag, installDistPref = Flag "dist", installUseWrapper = Flag False, installInPlace = Flag False, installVerbosity = Flag Normal}

p0 の説明文から推測すると、p3 .. p5 は installVerbosity フィールドのフラグを操作する関数だろう。p3 .. p5 のうち関数の型から見て一番簡単なのは p5 :: a -> [Maybe String] だ。これは installVerbosity フィールドの値を取り出す関数だろう。

*OptionViewers> p5 defaultInstallFlags
[Just "1"]

defaultInstallFlags の installVerbosity フィールドの値は Flag Normal だが、p5 の値は Just "1" になっている。p0 の説明をみると、デフォールトの verbosity level が 1 になっているので納得できる。

p4 :: a -> a は installVerbosity のフラグを直接操作する関数だ。defaultInstallFlags に関数適用してみると次のようになる。

*OptionViewers> p4 defaultInstallFlags
InstallFlags {installPackageDB = NoFlag, installDistPref = Flag "dist", installUseWrapper = Flag False, installInPlace = Flag False, installVerbosity = Flag Verbose}

installVerbosity フィールドの値が Flag Verbose になっている。また次のように p4 を複数回適用しても installVerbosity フィールドの値は変わらないので、p4 は単にフラグを Flag Verbose にセットするだけなのだろう。

*OptionViewers> p4 $ p4 defaultInstallFlags
InstallFlags {installPackageDB = NoFlag, installDistPref = Flag "dist", installUseWrapper = Flag False, installInPlace = Flag False, installVerbosity = Flag Verbose}

p3 :: ReadE (a -> a) は installVerbosity をコマンドライン引数の値にセットする ReadE 型のパーサだ。扱いが複雑なので段階的に見てみる。まず文字のリスト "3" を p3 で単純にパースしてみる。

*OptionViewers> :t runReadE p3 "3"
runReadE p3 "3"
  :: Either
        Distribution.ReadE.ErrorMsg (InstallFlags -> InstallFlags)

パースの結果は Either 型にラッピングされているので、中のフラグ操作の関数を取り出すためにはパターンマッチで関数を取り出して defaultInstallFlags に関数適用してやらなくてはならない。

*OptionViewers> let flag = fl defaultInstallFlags where (Right fl) = runReadE p3 "3"

取り出した flag は次のようになる。installVerbosity の値が Flag Deafening になっているのが分かる。

*OptionViewers> flag
InstallFlags {installPackageDB = NoFlag, installDistPref = Flag "dist", installUseWrapper = Flag False, installInPlace = Flag False, installVerbosity = Flag Deafening}

パースがエラーになる場合も試してみた。

*OptionViewers> let msg = m where (Left m) = runReadE p3 ""
*OptionViewers> msg
"Can't parse verbosity "

OptArgs コンストラクタの使い方が何となく分かってきた。

最後に BoolOpt を調べる。InstallCommand の commandOptions フィールドに含まれる [OptionField InstallFlags] の名前を調べると次のようになる。

*OptionViewers> viewOptionField installCommand
["verbose","builddir","inplace","shell-wrappers","package-db"]

また、installCommand に含まれる OptDescr 型のデータは次のようになる。

*OptionViewers> map optDescrType $ getOptDscr installCommand
["OptArg","ReqArg","ChoiceOpt","BoolOpt","ChoiceOpt"]

OptionField の数と OptDescr の数が一致しているので、それぞれの OptionField が1個だけの OptDescr を持っていると考えて良い。

BoolOpt は "shell-wrappers" OptionField に含まれている。これを hoge にバインドする。

*OptionViewers> let hoge = (getOptDscr installCommand) !! 3
*OptionViewers> optDescrType hoge
"BoolOpt"

BoolOpt コンストラクタの型は次のようになる。

BoolOpt
  :: Description
    -> OptFlags
    -> OptFlags
    -> (Bool -> a -> a)
    -> (a -> Maybe Bool)
    -> OptDescr a

BoolOpt のパラメータは、パターンマッチで取り出すことができる。

*OptionViewers> let (b0,b1,b2,b3,b4) = (c0,c1,c2,c3,c4) where (BoolOpt c0 c1 c2 c3 c4) = hoge

b0 .. b3 は説明だ。b2 と b3 は同じデータになっている。

*OptionViewers> b0
"using shell script wrappers around executables"
*OptionViewers> b1
("",["enable-shell-wrappers"])
*OptionViewers> b2
("",["disable-shell-wrappers"])

b3, b4 は InstallFlags のフラグを操作する関数だ。defaultInstallFlags の設定は次のようになっているが、

*OptionViewers> defaultInstallFlags
InstallFlags {installPackageDB = NoFlag, installDistPref = Flag "dist", installUseWrapper = Flag False, installInPlace = Flag False, installVerbosity = Flag Normal}

b3 の引数に True を与えて defaultInstallFlags に適用すると次のようになる。

*OptionViewers> b3 True defaultInstallFlags
InstallFlags {installPackageDB = NoFlag, installDistPref = Flag "dist", installUseWrapper = Flag True, installInPlace = Flag False, installVerbosity = Flag Normal}

installUseWrapper が Flag True に変化している。

b3 は installUseWrapper の値を取得する。

*OptionViewers> b4 defaultInstallFlags
Just False

これで、ReqArg, OptArg, ChoiceOpt, BoolOpt のパラメータについてひと通り見たことになる。
[PR]
by tnomura9 | 2014-01-26 11:34 | Haskell | Comments(0)

阿部首相のイルカ漁にたいする発言について

阿部首相がイルカ漁を擁護して、文化の違いなのだから容認してほしいと発言したということだが、この人は外交に対するセンスが全くないのではないかと疑いたくなる。

イルカについては人間より体重比の大きい脳を持ち、近年は言語を持っている可能性も指摘されている。欧米のイルカ保護の立場はこれらの科学的発見から、イルカを人間に準じる物と考えているのではないだろうか。そうであれば、イルカ漁に対する批判は、人権に準じる大きな問題としてとらえているのかもしれない。

そうであれば単に文化や習慣の問題として片付けることはできなくなってくる。反論するにしても相手の論点を十分に理解した上で、それに対する発言をしていかなければならない。

朴大統領の慰安婦についての執拗な攻撃も、女性が戦争の道具として使われたということに対する非難を含めているのかもしれない。そうなると、韓国ではもっとひどいことをしているとか戦争にはつきものだという反論は論点がずれている。

どんな事情があったにせよ売春婦の立場というものは自分ですすんで選択するような代物ではない。それについての遺憾の意ははっきりと表明し、(だからといって制度的に売春を強要したなどという捏造記事に基づく根拠のない非難や過去の政府間で解決済みの賠償に応じる必要は全くない)、戦後の自衛隊についてはそのような物は一切ないことを表明する必要があるのではないだろうか。

韓国の執拗な攻撃によって、日本もきちんと主張すべき所は主張していかなければならないことは明らかになった。しかし、その主張が内輪の納得でしかないのであれば、日本にたいする世界の評価が下がるだけだ。少なくとも相手の論点を理解して、それに対する適切な発言をしていくという最低限の努力はしていかなければならない。

日本の第二次世界大戦の顛末をみていると、このような内向きの判断による対応の間違いが随所に見られるように思うが、その教訓が生かされていないのではないだろうかと不安になってくる。また、戦後の慰安婦問題に対する日本人によるねつ造記事がこれだけの禍根をのこしているのを見ても、言葉がもたらす結果の重要性というものにもっと敏感になる必要がある。
[PR]
by tnomura9 | 2014-01-25 22:59 | 話のネタ | Comments(0)

commandOptions

前へ 目次 次へ

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

ひきつづき Distribution.Simple.Command の Option Descriptions セクションを見てみる。

前回は OptDescr 型の ChoiceOpt を調べた。特に関数型のパラメータが、フラグセットに値をセットしたりフラグセットのフラグの値を取り出したりする関数だという事を述べた。残る3つのコンストラクタ ReqArg, OptArg, BoolOpt のパラメータについて調べないといけないが、それらの実例を確保する必要がある。

以前に述べたように、Distribution.Simple.Setup には多くの Cabal のサブコマンドについてのフラグセットや CommandUI 型の定義になっている。トップレベルのグローバルコマンドのフラグセットは defaultGlobalFlags で、CommandUI 抽象は globalCommand だった。これらのデータは CoiceOpt コンストラクタ型のデータを調べるのに使った。

そのほかのコマンドで扱いやすそうなのは install コマンドだ。install コマンドのフラグセットは InstallFlags 型の defaultInstallFlags だが、InstallFlags が Show クラスのインスタンスなので、その中身を ghci で直接に確認できる。

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

ghci> defaultInstallFlags
InstallFlags {installPackageDB = NoFlag, installDistPref = Flag "dist", installUseWrapper = Flag False, installInPlace = Flag False, installVerbosity = Flag Normal}

次に installCommand のなかから OptDescr 型を取り出す事を考えよう。commandOptions アクセサを使えば installCommand の中の [OptionField a] 型のリストを取り出す事ができる。

ghci> :t commandOptions installCommand
commandOptions installCommand
  :: ShowOrParseArgs -> [OptionField InstallFlags]

commandOptions フィールドの値は ShowOrParseArgs 型の引数をとる関数なので引数に ShowArgs コンストラクタを与えてやる。

ghci> :t (commandOptions installCommand) ShowArgs
(commandOptions installCommand) ShowArgs
  :: [OptionField InstallFlags]

OptionField 型の構成は次のようになるので、

ghci> :info OptionField
data OptionField a
  = OptionField {optionName :: Name, optionDescr :: [OptDescr a]}

[OptionField installFlags] の optionName フィールドの値を調べてみる。

ghci> map optionName $ (commandOptions installCommand) ShowArgs
["verbose","builddir","inplace","shell-wrappers","package-db"]

今度は [OptionField installFlags] の中の optionDescr を取り出して foo にバインドする。

ghci> let foo = concatMap optionDescr $ (commandOptions installCommand) ShowArgs

foo の型を調べてみると確かに OptDescr installFlags 型のリストになっているのが分かる。

ghci> :t foo
foo :: [OptDescr InstallFlags]

この中に ReqArg コンストラクタ型のデータが含まれているかどうか見つけてみる。ReqArg コンストラクタの構成は次のようになっており5つのパラメータを持っている。

ghci> :t ReqArg
ReqArg
  :: Description
     -> OptFlags
     -> ArgPlaceHolder
     -> Distribution.ReadE.ReadE (a -> a)
     -> (a -> [String])
     -> OptDescr a

従って ReqArg コンストラクタが含まれていつかどうか判別する関数を isReqArg として次のように定義する。

ghci> :set +m
ghci> let
Prelude Distribution.Simple.Command Distribution.Simple.Setup| isReqArg (ReqArg _ _ _ _ _) = True
Prelude Distribution.Simple.Command Distribution.Simple.Setup| isReqArg _ = False
Prelude Distribution.Simple.Command Distribution.Simple.Setup|
ghci>

isReqArg 関数を使って foo をテストすると次のようになる。

ghci> map isReqArg foo
[False,True,False,False,False]

どうやら foo の2番目の要素が ReqArg コンストラクタ型の OptDescr installFlags 型データのようだ。これを取り出して bar にバインドする。

ghci> :unset +m
ghci> let bar = foo !! 1
ghci> :t bar
bar :: OptDescr InstallFlags
ghci> isReqArg bar
True

上で調べたように ReqArg コンストラクタの5番目のパラメータは a -> [String] 型の関数なのでこれはフラグから値を取り出す関数のように見える。そこでこれを取り出して baz にバインドする。

ghci> let baz = fnc where (ReqArg _ _ _ _ fnc) = bar
ghci> :t baz
baz :: InstallFlags -> [String]

baz を defaultInstallFlags に関数適用してみた。

ghci> baz defaultInstallFlags
["dist"]

defaultInstallFlags フラグセットの installDistPref フラグの値が取り出せた。

ReqArg コンストラクタの4番目のパラメータは Distribution.ReadE.ReadE (a -> a) 型のデータだがこれは ReadE 型のパーサだ。従って、

runReadE "hello"

で文字列 "hello" をパースした結果を取り出す事ができる。ReadE 型のパーサについてはこのシリーズの以前の記事で調べている。

ReqArg 型からまずこのパーサを取り出してテストする方法を考えるが、その前に ReadE パーサが使えるようにするためにつぎの2つの標準モジュールをインポートする必要がある。

ghci> import Distribution.Compat.ReadP
ghci> import Distribution.ReadE

そこで、次のようにして bar の4番目のパラメータを取り出して foobar にバインドする。

ghci> let foobar = f1 where (ReqArg _ _ _ f1 _) = bar

まず foobar の型を確かめる。

foobar :: ReadE (InstallFlags -> InstallFlags)

確かに ReadE 型のパーサーであることがわかる。このパーサを使って文字列 "/var/dist" をパースすると戻り値の型は次のようになる。

ghci> :t runReadE foobar "/var/dist"
runReadE foobar "/var/dist"
  :: Either
      Distribution.ReadE.ErrorMsg (InstallFlags -> InstallFlags)

そこで Either 型にラッピングされている InstallFlags -> InstallFlags 型の関数を取り出して foobaz にバインドする。

ghci> let foobaz = setter where Right setter = runReadE foobar "/var/dist"

foobaz を defaultInstallFlags に関数適用するが、まず defaultInstallFlags の内容を確認しておく。

ghci> defaultInstallFlags
InstallFlags {installPackageDB = NoFlag, installDistPref = Flag "dist", installUseWrapper = Flag False, installInPlace = Flag False, installVerbosity = Flag Normal}

foobaz を defaultInstallFlags に関数適用してみると次のようになる。

ghci> foobaz defaultInstallFlags
InstallFlags {installPackageDB = NoFlag, installDistPref = Flag "/var/dist", installUseWrapper = Flag False, installInPlace = Flag False, installVerbosity = Flag Normal}

defaultInstallFlags の installDistPref が Flag "dist" から "/var/dist" に変更されているのが分かる。

したがって、上の記事で取り出した OptDescr 型のデータ bar の第4パラメータは installDistPref フィールドの引数を取り出すためのパーサで、第5パラメータは installDistPref フィールドのパラメータを取り出すための関数であることが分かった。第4のパラメータを第5のパラメータは互いに逆方向の処理を installDistPref フィールドに行うパーサと関数だった。
[PR]
by tnomura9 | 2014-01-23 20:56 | Haskell | Comments(0)

反日と嫌韓

朴大統領の執拗な反日外交に嫌気がさして、嫌韓の記事を読むのが面白いと感じるようになった。反日のせいで韓国の経済が傾いているそうだとか、韓国の製品は日本からの部品の供給が途絶えると製造すらできなくなるなどの記事を読んで溜飲を下げている。

しかし、よく考えると朴大統領の反日政策が非現実的で韓国を窮地に追いやるかもしれないという事と、嫌韓の記事と共通するものがあるような気がしてきた。どちらも結論が先にあって、いろいろな事実はその結論を支持するためだけに集められているという事だ。現実はどういうことなのかという考え方がなくなってしまっている。

たとえば、尖閣のせめぎ合いを巡る日中の軍事力比較で、中国は艦船の数は多いが、練度と技術力は日本が優位に立っており、開戦したら日本が圧倒的な勝利を得るという意見が見られるが、戦争などという物はやってみなければ結果は分からないのだ。したがって、やらないにこした事はない。日本が艦船や航空機の数が少なくても中国に勝てるという意見の中に、日露戦争のバルチック艦隊を連合艦隊が打ち破ったという歴史を根拠にしているとしたら、こんなに非現実な考え方はない。

韓国の話にもどるが、反日行動のせいで韓国の経済が破綻するとは思えないし、もしそうであっても韓国の国力が落ちて、北朝鮮に制圧されたとしたら、日本は直接中国や北朝鮮と国境を隔てて対峙しなくてはならなくなる。また、想定される多量の韓国からの難民の対策に追われることになるだろう。韓国を好きになれといわれても難しいかもしれないが、嫌韓だから韓国は滅びてしまえとは簡単には言えないのだ。

韓国の反日の問題点はその主張が内向的だということだ。外的な条件を考慮せずひたすら自身の主張が実現化する事を願って行動している。現実よりも自己の主義主張が通る事が大切なのだ。これは、中国共産党の主張にも共通している。おそらく、東アジアのメンタリティがそういうものなのではないだろうか。

おそろしいことに、日本にも同じメンタリティがあるような気がする。それは、「質の良い製品を作れば必ず売れるはずだ」とか「安ければ必ず売れるはずだ」という信念だ。日本の失われた20年はマネーゲームよりも販売不振によるものが多かったのではないだろうか。東アジアの民族の内向的な性質が、販売の現場で市場の意向を汲み取ってそれに合わせた製品を用意する事よりも、このような良い製品なのだから買わないのはおかしいという行動に導いてしまったのではないか。

こういう例は他にもある。製造拠点を中国から引き上げて、東南アジアに移せばいいではないかという意見があるが、東南アジアの経済を支配しているのは華僑だ。残念な事に、中国には華僑がいるが、日本にはいない。現地の事情を熟知したサポートシステムがないのだ。

グローバリゼーションに対応するために英語教育をするということだが、東アジア民族の内向的な考え方を現実主義的な考え方に変える必要もあるのではないだろうか。教育やマスコミの力は侮れない物があるが、嫌韓に走らず、日本の将来を見据えた記事をマスコミにみつけたいものだ。
[PR]
by tnomura9 | 2014-01-23 05:56 | 話のネタ | Comments(0)

OptDescr

前へ 目次 次へ

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

今回は Distribution.Simple.Command の Option Descriptions セクションを見てみる。

OptDesr a 型はオプションの情報を統合している。パラメータの型変数 a はそれぞれのオプションのフラグセットの型になる。たとえば Distribution.Simple.Setup で定義されている globalCommand のオプションのフラグセットの型は GlobalFlags だ。

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

ghci> :info globalCommand
globalCommand :: CommandUI GlobalFlags
      -- Defined in `Distribution.Simple.Setup'

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

GlobalFlags は globalVersion と globalNumericVersion という2つのフィールドに Flag Bool 型の値を納めたフラグセットであることがわかる。Flag 型のパラメータは型変数なので様々な型の値を納める事ができる。フラグの多様性に対応するためだ。また、様々なクラスのインスタンスになっているが、フラグ同士の演算を行うことができるようにするためだ。

data Flag a = Flag a | NoFlag
       -- Defined in `Distribution.Simple.Setup'
instance Bounded a => Bounded (Flag a)
  -- Defined in `Distribution.Simple.Setup'
instance Enum a => Enum (Flag a)
  -- Defined in `Distribution.Simple.Setup'
instance Eq a => Eq (Flag a)
  -- Defined in `Distribution.Simple.Setup'
instance Functor Flag -- Defined in `Distribution.Simple.Setup'
instance Read a => Read (Flag a)
  -- Defined in `Distribution.Simple.Setup'
instance Show a => Show (Flag a)
  -- Defined in `Distribution.Simple.Setup'

Cabal のコマンドを抽象したものが CommandUI 型だが、OptDscr 型は CommandUI のどのフィールドにおさめられているのだろうか。CommandUI 型は次のようになる。

ghci> :info CommandUI
data CommandUI flags
  = CommandUI {commandName :: String,
               commandSynopsis :: String,
               commandUsage :: String -> String,
               commandDescription :: Maybe (String -> String),
               commandDefaultFlags :: flags,
               commandOptions :: ShowOrParseArgs -> [OptionField flags]}
      -- Defined in `Distribution.Simple.Command'

CommandUI 型のフィールドの中には OptDescr 型は現れていないが、commandOptions フィールドの OptionField flags 型に含まれている。OptionField 型の構成は次のようになっている。

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

これをみると OptionField 型の optionDescr フィールドの値が OptDesr a 型のリストになっている事が分かる。OptDescr 型の構成は次のようになっている。

ghci> :info OptDescr
data OptDescr a
  = ReqArg Description
           OptFlags
           ArgPlaceHolder
           (Distribution.ReadE.ReadE (a -> a))
           (a -> [String])
  | OptArg Description
           OptFlags
           ArgPlaceHolder
           (Distribution.ReadE.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)
      -- Defined in `Distribution.Simple.Command'

OptDescr 型としてひとくくりにされているが、オプションの性質によって4つのコンストラクタに分けられている事が分かる。ReqArg は引数を必要とするオプションのコンストラクタだ。OptArgs は 0 個以上の引数を必要とするオプションのコンストラクタだ。ChoiceOpt はオプションを選択したり、選択しなかったりするオプションの場合のコンストラクタだ。BoolOpt はオプションが True または False の値を持つ場合のコンストラクタだ。

OptDescr の各コンストラクタのパラメータの型は type 宣言された既存の型の別名だ。Description はオプションの説明文の String 型だ。

ghci> :info Description
type Description = String
      -- Defined in `Distribution.Simple.Command'

OptFlags はショートフラグのリストと、ロングフラグのリストのペアだ。

ghci> :info OptFlags
type OptFlags = (SFlags, LFlags)
      -- Defined in `Distribution.Simple.Command'

SFlags はショートフラグを表す英文字のリストだ。

ghci> :info SFlags
type SFlags = [Char]    -- Defined in `Distribution.Simple.Command'

LFlags はロングフラグの文字列のリスト。

ghci> :info LFlags
type LFlags = [String]    -- Defined in `Distribution.Simple.Command'
ghci> :info ArgPlaceHolder

ArgPlaceHolder はオプションの引数になる文字列。placeholder は編集作業のとき写真や絵をいれるために確保しておく枠の事。

type ArgPlaceHolder = String
      -- Defined in `Distribution.Simple.Command'

これらの説明的なパラメータをのぞいた、関数のパラメータに OptDescr の機能が納められている。とはいえ、OptDescr の機能の実体は何なのだろうか。ChoiceOpt [(Description, OptFlags, a -> a, a -> Bool)] を例にとって調べてみる。

Distribution.Simple.Setup を見ても OptDescr を直接コーディングしている例はない。オプション関係のデータは option 関数で作られている。そこで、Distribution.Simple.Setup の globalCommand のコーディングをまねて OptionField 型のデータを作ってみた。

ghci> let foo = option ['V'] ["version"] "Print version information" globalVersion (\v flags -> flags { globalVersion = v }) trueArg

OptDescr 型のリストは次のように OptionField 型の optionDescr フィールドに納められている。

ghci> :info OptionField
data OptionField a
  = OptionField {optionName :: Name, optionDescr :: [OptDescr a]}

そこで、OptionField 型のデータ foo から OptDescr a 型を取り出して bar にバインドする。

ghci> let bar = head $ optionDescr foo

詳細は省くが bar のコンストラクタは ChoiceOpt なのでパラメータは次のようになる。

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

そこでパターンマッチでパラメータを取り出し、さらにパラメータの先頭の要素のタプルの Description, OptFlags, a -> a, a -> Bool を取り出した。

ghci> let ChoiceOpt baz = bar
ghci> let (d, f, f1, f2) = head baz

d と f はすぐに見てみることができる。

ghci> d
"Print version information"
ghci> f
("V",["version"])

f1 と f2 はいずれも GlobalFlags を操作する関数だが、GlobalFlags を show 関数で文字列化する事ができないので次のように showFlags 関数を定義する。

ghci> let showFlags flags = "{globalVersion = " ++ (show $ globalVersion flags) ++ ", globalNumericVersion = " ++ (show $ globalNumericVersion flags) ++ "}"

showFlags 関数を defaultGlobalFlags に関数適用すると defaultGlobalFlags の中をのぞいてみることができる。

ghci> showFlags defaultGlobalFlags
"{globalVersion = Flag False, globalNumericVersion = Flag False}"

これでやっと ChoiceOpt コンストラクタのパラメータの f1 :: a -> a の機能を試してみる事ができる。

ghci> showFlags $ f1 defaultGlobalFlags
"{globalVersion = Flag True, globalNumericVersion = Flag False}"

f1 は defaultGlobalFlags の globalVersion フラグを Flag True にセット (set) する関数だった。

f2 を defaultGlobalFlags に適用すると False が返ってくるが、これは globalVersion のフラグの値をとりだしている。

ghci> f2 defaultGlobalFlags
False

OptDescr 型の関数型のパラメータは、フラグセットを操作する関数だった。他のタイプのコンストラクタの機能も上で行ったような実験で確かめてみる事ができるが、記事が長くなってきたので次回の記事で述べる。
[PR]
by tnomura9 | 2014-01-19 19:59 | Haskell | Comments(0)

liftOption 関数

前へ 目次 次へ

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

今回は Distribution.Simple.Command の Liftings & Projections セクションを見てみる。

iftOption 関数の型は次のようになるが、これだけではどういう働きをするのか推測できない。ただし、引数の1つに OptionField a があり、戻値が OptionField b なので、OptionField の変換を行う関数ではないかと思われる。

liftOption :: (b -> a) -> (a -> b -> b) -> OptionField a -> OptionField b

liftOption のソースは次のようになる。

liftOption :: (b -> a) -> (a -> (b -> b)) -> OptionField a -> OptionField b
liftOption get' set' opt = opt { optionDescr = liftOptDescr get' set' `map` optionDescr opt}


liftOptDescr :: (b -> a) -> (a -> (b -> b)) -> OptDescr a -> OptDescr b
liftOptDescr get' set' (ChoiceOpt opts) =
    ChoiceOpt [ (d, ff, liftSet get' set' set , (get . get'))
              | (d, ff, set, get) <- opts]

liftOptDescr get' set' (OptArg d ff ad set def get) =
    OptArg d ff ad (liftSet get' set' `fmap` set) (liftSet get' set' def) (get . get')

liftOptDescr get' set' (ReqArg d ff ad set get) =
    ReqArg d ff ad (liftSet get' set' `fmap` set) (get . get')

liftOptDescr get' set' (BoolOpt d ffT ffF set get) =
    BoolOpt d ffT ffF (liftSet get' set' . set) (get . get')

liftSet :: (b -> a) -> (a -> (b -> b)) -> (a -> a) -> b -> b
liftSet get' set' set x = set' (set $ get' x) x

liftOption 関数の型から考えて引数の opt は OptionField 型だ。OptionField 型のフィールドは次のようになっている。

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

したがって、liftOption の定義の次の部分は、第3引数の OptionField 型のデータの optionDescr フィールドの部分を liftOptDsecr 関数を使って書き換えて戻値の OptionField 型データを作成するという意味になる。

opt { optionDescr = liftOptDescr get' set' `map` optionDescr opt} 

optionDescr opt は OptionField 型のデータ opt の optionDesr フィールドの [OptDescr a] リストを取り出している。このリストに map 関数を用いて適用する関数 liftOptDescr get' set' は OptDescr a 型のデータを引数にする関数だ。

liftOptDescr 関数の値は、引数の OptDescr 型のパターンごとに定義されている。OptDescr 型には次のように4つのコンストラクタがあるので、liftOptDescr の定義もそれぞれのコンストラクタ毎の4種類の定義がなされている。

ghci> :info OptDescr
data OptDescr a
  = ReqArg Description
           OptFlags
           ArgPlaceHolder
           (Distribution.ReadE.ReadE (a -> a))
           (a -> [String])
  | OptArg Description
           OptFlags
           ArgPlaceHolder
           (Distribution.ReadE.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)
        -- Defined in `Distribution.Simple.Command'

liftSet 関数は get set get' set' の4つの関数から合成された liftSet 関数を作り出すためのものだ。

まだ OptDescr 型の詳細については調べていないので、この記事では liftOption の定義の構成要素の概略を調べるに留める。

Liftings & Projections セクションには liftOption 関数の他に

viewAsFieldDescr :: OptionField a -> FieldDescr a

関数が記述されているが、これを使った具体例のコードがまた未調査であることから、今回はスキップする。
[PR]
by tnomura9 | 2014-01-18 01:02 | Haskell | Comments(0)

option

前へ 目次 次へ

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

今回は Distribution.Simple.Command の Option Fields セクションを見てみる。

OptionField 型はオプションの情報を納めるデータ型だ。コマンドの CommandUI 抽象の commandOptions フィールドに [OptionField flags] リストがはいる。OptionField 型は次のように定義されている。

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

optionName フィールドにはオプション名、optionDescr フィールドにはオプションを表すデータのリストが入っている。Name 型は type 宣言された String 型の別名だ。OptDescr 型は次のように定義されている。

data OptDescr a
  = ReqArg Description
           OptFlags
           ArgPlaceHolder
           (Distribution.ReadE.ReadE (a -> a))
           (a -> [String])
  | OptArg Description
           OptFlags
           ArgPlaceHolder
           (Distribution.ReadE.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)
        -- Defined in `Distribution.Simple.Command'

OptDescr 型のコンストラクタのうち ReqArg は引数を持つオプションの情報を保持している。OptArg はオブションが付加的な引数を保つ場合、ChoiceOpt はオプションが選択できる場合、BoolOpt はオプションによってフラグが True または False になる場合のコンストラクタだ。

OptDescr の詳しい説明は Distribution.Simple.Command の Option Description セクションに出てくるのでここでは立ち入らない。

OptionField 型の値はコンストラクタで直接作成するのではなく option 関数を用いて作る。option 関数の型は次のようになる。

ghci> :info option
option ::
  SFlags
  -> LFlags
  -> Description
  -> get
  -> set
  -> MkOptDescr get set a
  -> OptionField a
        -- Defined in `Distribution.Simple.Command'

SFlags はショートオプションのリスト、LFlags はロングオプションのリスト、Description は説明、get はGlobalFlags のようなフラグセットのフィールド名でそのフィールドのフラグの値を取り出すためのもの。set は特定のフィールドのフラグに値を設定するための関数。MkOptDescr get set a は、どのコンストラクタを使ってオプションを Descr a 型にするかを指定するためのものだ。

MkOptDesr 型は次のように type 宣言された ReqOpt などの OptDescr 型のコンストラクタでデータ型を作るための関数 OptDescr smart constructors の型の別名だ。

type MkOptDescr get set a =
  SFlags -> LFlags -> Description -> get -> set -> OptDescr a
        -- Defined in `Distribution.Simple.Command'

option 関数の利用例は、Distribution.Simple.Setup の globalCommand の定義に見ることができる。globalCommand の定義のコードは次のようになる。

globalCommand :: CommandUI GlobalFlags
globalCommand = CommandUI {
    commandName         = "",
    commandSynopsis     = "",
    commandUsage        = \_ ->
         "This Setup program uses the Haskell Cabal Infrastructure.\n"
      ++ "See http://www.haskell.org/cabal/ for more information.\n",
    commandDescription  = Just $ \pname ->
         "For more information about a command use\n"
      ++ "  " ++ pname ++ " COMMAND --help\n\n"
      ++ "Typical steps for installing Cabal packages:\n"
      ++ concat [ "  " ++ pname ++ " " ++ x ++ "\n"
                | x <- ["configure", "build", "install"]],
    commandDefaultFlags = defaultGlobalFlags,
    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
      ]
  }

commandOptions フィールドに2つの option 関数の用例が見られる。どちらも同じタイプなので最初の option 関数の用例をみてみる。この例では、SFlags = ['V']、LFlags = ["version"]、Description = "Print version information"、get = globalVersion、put = (\v flags -> flags {globalVersion = v})、MkOptDesc = trueArg だ。

trueArg は Distribution.Simple.Setup で次のように noArg を使って定義されている。

trueArg = noArg (Flag True)

noArg は Distribution.Simple.Command で定義され、そのコードは次のようになる。

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

choiceOpt 関数は引数の値をもとに 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]

choiceOpt 関数などの OptDescr 型の値を作る関数は Distribution.Simple.Command の OptDescr smart constructors セクションに記述されている。

multiOption 関数は複数の OptDescrs をもつオプションを作成するための関数で、Distribution.Simple.Command に次のように定義されている。

-- | Create an option taking several OptDescrs.
--   You will have to give the flags and description individually to the OptDescr constructor.
multiOption :: Name -> get -> set
            -> [get -> set -> OptDescr a]  -- ^MkOptDescr constructors partially applied to flags and description.
            -> OptionField a
multiOption n get set args = OptionField n [arg get set | arg <- args]
[PR]
by tnomura9 | 2014-01-13 15:17 | Haskell | Comments(0)

commandsRun

前へ 目次 次へ

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

今回は Distribution.Simple.Command の Running commands セクションを見てみる。

このセクションで記述されているのは commandsRun 関数一つだけだ。commandsRun 関数の型は次のようになる。

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

ghci> :info commandsRun
commandsRun ::
  CommandUI a
  -> [Command action]
  -> [String]
  -> CommandParse (a, CommandParse action)
        -- Defined in `Distribution.Simple.Command'

ひとつのコマンドの情報の全てを保持する CommandUI 型のデータと、そのコマンドのサブコマンドのリストの [Command action] と コマンド・ラインから入力されたオプションのリスト [String] を引数に取り、コマンドラインオプションのパースの結果である CommandParse (a, CommandParse action) を返す。

コマンドの実行は commandsRun では行われない。実際のコマンドの実行は、commandsRun 関数の戻値を元にして Main.hs の mainWorker 関数の中で行われている。

mainWorker :: [String] -> IO ()
mainWorker ("win32selfupgrade":args) = win32SelfUpgradeAction args
mainWorker args = topHandler $
  case commandsRun globalCommand commands args of
    CommandHelp   help                 -> printGlobalHelp help
    CommandList   opts                 -> printOptionsList opts
    CommandErrors errs                 -> printErrors errs
    CommandReadyToGo (globalflags, commandParse)  ->
      case commandParse of
        _ | fromFlag (globalVersion globalflags)        -> printVersion
          | fromFlag (globalNumericVersion globalflags) -> printNumericVersion
        CommandHelp     help           -> printCommandHelp help
        CommandList     opts           -> printOptionsList opts
        CommandErrors   errs           -> printErrors errs
        CommandReadyToGo action        -> action globalflags

CommandParse action 型の CommnadReadyToGo などのコンストラクタのパターンマッチ時に実行されている関数 action globalflags などが実際の処理を実行している。

commandsRun のソースの中心部分は次のようになる。

commandsRun :: CommandUI a
            -> [Command action]
            -> [String]
            -> CommandParse (a, CommandParse action)
commandsRun globalCommand commands args =
  case commandParseArgs globalCommand' True args of
    CommandHelp      help          -> CommandHelp help
    CommandList      opts          -> CommandList (opts ++ commandNames)
    CommandErrors    errs          -> CommandErrors errs
    CommandReadyToGo (mkflags, args') -> case args' of
      ("help":cmdArgs) -> handleHelpCommand cmdArgs
      (name:cmdArgs) -> case lookupCommand name of
        [Command _ _ action] -> CommandReadyToGo (flags, action cmdArgs)
        _                    -> CommandReadyToGo (flags, badCommand name)
      []                     -> CommandReadyToGo (flags, noCommand)
     where flags = mkflags (commandDefaultFlags globalCommand)

上のコードでは、最初に globalCommnad' :: CommandUI flags と args :: [String] の情報を使ってコマンド・ライン・オプションをぱーすする仕事を commandParseArgs 関数に丸投げしている。

commandParseArgs 関数の型は次のようになる。

commandParseArgs ::
  CommandUI flags
  -> Bool -> [String] -> CommandParse (flags -> flags, [String])
        -- Defined in `Distribution.Simple.Command'

また、戻値の CommandParse 型のコンストラクタは次のようになる。

data CommandParse flags
  = CommandHelp (String -> String)
  | CommandList [String]
  | CommandErrors [String]
  | CommandReadyToGo flags
        -- Defined in `Distribution.Simple.Command'
instance Functor CommandParse
  -- Defined in `Distribution.Simple.Command'

commandsRun では commandParseArgs 関数からの戻値のコンストラクタが CommandHelp, CommandList, CommandErrors の時はほぼそのままを自身の戻値として返す。

commandParseArgs からの戻値のコンストラクタが commandReadyToGo の時は、commandParseArgs でオプションの解析をした後に残った引数のリスト args' の先頭の要素を調べる。

先頭の要素が "help" の時はヘルプメッセージの処理をする。$> cabal help で動作確認できる。

先頭の要素が "help" ではない場合は name として取り出して lookupCommand 関数に渡し、name がサブコマンドのリスト [Command action] に含まれているかどうかを検索する。name がサブコマンドのリストに含まれていたらそのリスト要素を commandReadyToGo のコンストラクタでラッピングして返す。それ以外は badCommnad か noCommand の処理をする。lookupCommnad 関数のコードは次のようになる。

    lookupCommand cname = [ cmd | cmd@(Command cname' _ _) <- commands', cname'==cname ]

まとめると、commandsRun はコマンドの CommnadUI 抽象とサブコマンドのリスト [Command action] とコマンド・ライン・オプションのリスト [String] を元に、コマンドラインオプションをパースして、CommnadParse 型の値を返すという仕事をしているということが分かる。その際、コマンドライン・オプションのパースの処理は commandParseArgs 関数が行い、commandsRun の主な仕事は、サブコマンドがサブコマンドのリストにあるかどうかを判別して処理することだ。

cabal-install のコマンド・ライン・オプションの処理は commandsRun と commandParseArgs が主要な処理をしているので、コードの細部についての検討が必要だが、これらの関数に利用されている関数には Distribution.Simple.Commnad でエクスポートされていないものも多く簡単には ghci で検証できない。詳細についての検討は後日に回す。
[PR]
by tnomura9 | 2014-01-13 10:08 | Haskell | Comments(0)

commandAddAction

前へ 目次 次へ

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

今回は Distribution.Simple.Command の Associating actions with commands セクションを見てみる。

Command action 型は、コンストラクタが Command で第1パラメータが String 第2パラメータが String 第3パラメータが [String] -> CommandPars action 型の関数だ。

第1のパラメータと第2のパラメータの説明がソースにはないが、commandAddAction の定義を見ると第1のパラメータは CommandUI 抽象の commandName フィールドの文字列で、第2のパラメータは commandSynopsis フィールドの文字列だ。第3のパラメータについては、commandAddAction 解読の時に調べる。

ghci> :info Command
data Command action
  = Distribution.Simple.Command.Command String
                                        String
                                        ([String] -> CommandParse action)
        -- Defined in `Distribution.Simple.Command'

commandAddAction 関数の型は次のようになる。

ghci> :info commandAddAction
commandAddAction ::
  CommandUI flags -> (flags -> [String] -> action) -> Command action
        -- Defined in `Distribution.Simple.Command'

第1の引数はコマンドの CommandUI 抽象で、第2の引数は (flags -> [String] -> action) 型の関数だ。戻値は Command action 型になる。

commandAddAction のコードは次のようになるがかなり短い。

commandAddAction :: CommandUI flags
                 -> (flags -> [String] -> action)
                 -> Command action
commandAddAction command action =
  Command (commandName command)
          (commandSynopsis command)
          (fmap (uncurry applyDefaultArgs)
         . commandParseArgs command False)

  where applyDefaultArgs mkflags args =
          let flags = mkflags (commandDefaultFlags command)
           in action flags args

commandAddAction 関数は CommnadUI 型の command と (flags -> [String] -> action) 型の関数 action を引数に取り Command action 型にラッピングして返す関数だ。

Command コンストラクタの第1パラメータは commnadName command で CommnadUI 型の commnad の commandName フィールドの文字列になる。第2パラメータは command の commandSynopsis フィールドの文字列だ。

第3のパラメータのコードは少々入り組んでいる。中心部分は、

fmap (uncurry applyDefaultArgs) . commandParseArgs command False

だ。commandParseArgs 関数の型は次のようになるから、

ghci> :info commandParseArgs
commandParseArgs ::
  CommandUI flags
  -> Bool -> [String] -> CommandParse (flags -> flags, [String])
        -- Defined in `Distribution.Simple.Command'

CommandUI 抽象と Bool 値とコマンド・ライン引数のリストを引数に取り CommandParse (flags -> flags, [String]) 型の値を返す。CommandParse コンストラクタのパラメータは flags -> flags 型の関数と [String]) 型のペアだ。

applyDefaultArgs 関数は mkflags と args の2変数関数だが、uncurry することによって (mkflags, args) のペアの1変数関数にすることができる。fmap を関数適用するので、CommandParse 型のパラメータに直接作用できる。

applyDefaultArgs 関数はローカルで定義されているが、commandDefaultFlags command で取り出した CommandUI 抽象のデフォールトのフラグを取り出し、それに mkflag :: flag -> flag 型の関数を適用して新しいフラグ flag をつくり、それをパースでマッチしなかったコマンド・ライン引数とともに action 関数に渡す。このことによって、オプションの適用されたアクション関数が作成される。

結果的に、commandAddAction 関数によって、コマンド・ライン・オプションのパースをしてそのオプションに対応したアクションを得る「関数」をいれた Command action 型を作ることができる。つまり、Command action 型のデータにはコマンド・ライン・オプションをパースしてそれにふさわしい action をとりだす機能が備わっているということだ。

commandAddAction 関数を具体的な例を使って確認することはできなかったが、commandAddAction の引数の action に相当する関数の定義が cabal-install の Main.hs にいくつか記述されているが、そのうちの listAction の定義を次に示す。

listAction :: ListFlags -> [String] -> GlobalFlags -> IO ()
listAction listFlags extraArgs globalFlags = do
  let verbosity = fromFlag (listVerbosity listFlags)
  config <- loadConfig verbosity (globalConfigFile globalFlags) mempty
  let configFlags  = savedConfigureFlags config
      globalFlags' = savedGlobalFlags    config `mappend` globalFlags
  (comp, conf) <- configCompilerAux' configFlags
  list verbosity
       (configPackageDB' configFlags)
       (globalRepos globalFlags')
       comp
       conf
       listFlags
       extraArgs

listFlags と extraArgs と globalFlags を引数にして IO () を返しているので listAction 関数は IO モナドだ。したがって do 記法を使ってプログラムされている。let 記法で verbosity, configFlags, globalFlags, comp, conf などのオプションの設定情報を取り出し、list モナド関数に渡している。listFlags, list 関数などは Distribution.Client.List.hs に記述されている。

list サブコマンドの実体はこの list 関数であることが分かる。コマンドオプションによる設定の情報は listFlags などのフラグセットの情報から取り出されて、引数としてこの関数にあたえられているようだ。

最後にもう一度 commandAddAction 関数がどういうことをしているかを要約してみよう。

1.引数として Cabal のサブコマンドの抽象である CommandUI 型の command と コマンドを実行するプログラムとその情報がカプセル化された action の2つのデータをとる。

2. 上のふたつのデータから、Command action 型の値を作って戻す。コンストラクタの Command の第1パラメータは CommandUI の commandName で第2パラメータ commandSynopsis だ。第3の引数は [String] -> action 型の関数だ。

3. 第3の引数の [String] -> action 型の関数の作り方はややトリッキーなので注意が必要だ。材料は、listCommand の場合、ListFlags -> [String] -> GlobalFlags -> IO () 型の listAction 関数と CommandUI 抽象の listCommnad とコマンド・ラインから取得するコマンド・ライン・オプションのリストの [String] だ。

最初に commandParseArgs 関数を使って、listCommnad を元に [String] をパースし、defaultListFlags のフラグ操作をする flag -> flag 型の関数とそのオプション以外のコマンド・ライン引数のリスト args のペア CommandParse (flag -> flag, args) のリストを作成する。

CommandParse (flag -> flag, args) に fmap を使って applyDefaultArgs' (mkflag, args) を関数適用して、デフォールトのフラグに commandParseArgs によるパース結果を適用した新しい flag と パースでマッチが起きなかった残りの引数 args を action :: listFlags -> [String] -> GlobalFlags に渡して [String] -> IO () 型の関数を作る。

ややこしいのは上の操作は実際にはポイントフリースタイルで行われるため、コマンド・ライン引数のリスト [String] は後で与えるようになっていることだ。つまりどんな [String] のコマンド・ライン・オプションのリストにも対応できる list コマンドの関数が作られて Command 型の第3パラメータに収められるということだ。

こういう抽象的なプログラムは解読する方は大変だが、しかし、list コマンドを実行するのにパーサのことも何も考えずに list コマンドの Command 型のデータの第3引数にコマンド・ライン・オプションのリストをそのまま与えるだけで、複雑なオプションの動作も含めた list コマンドが実行されるというのは、使う側からは非常に便利だ。

CommnadUI 型と action にパースの情報も含めたコマンドの情報が全てカプセル化されているため、新しいコマンドを導入しても、パーサをいじる必要が全くない。したがって、コマンドの追加や削除が非常に簡単にできる。

まだ Distribution.Simple を使って自前のコマンドを作成できるほど理解していないが、それでもこのモジュールを使いこなすことで、労少なくして非常に柔軟なコマンド体系の構築ができるのではないかと思われる。

こういうプログラムを自分で考え出すというのは不可能だが、Haskell のモジュール化プログラムの威力を感じることができる例だ。
[PR]
by tnomura9 | 2014-01-12 17:47 | Haskell | Comments(0)

makeCommand

前へ 目次 次へ

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

今回は Distribution.Simple.Command の Constructing commands セクションを見てみる。

最初は ShowOrParseArgs 型だ。この型はパラメータを取らない2つのコンストラクタからなっている。

ghci> :info ShowOrParseArgs
data ShowOrParseArgs = ShowArgs | ParseArgs
        -- Defined in `Distribution.Simple.Command'

ShowOrParseArgs の使い方は Haddock にもソースにも書かれていなかったが、次のように CommandUI 型の commandOptions フィールドのなかで使われている。

ghci> :info CommandUI
data CommandUI flags
  = CommandUI {commandName :: String,
               commandSynopsis :: String,
               commandUsage :: String -> String,
               commandDescription :: Maybe (String -> String),
               commandDefaultFlags :: flags,
               commandOptions :: ShowOrParseArgs -> [OptionField flags]}
        -- Defined in `Distribution.Simple.Command'

makeCommand 関数は5つの引数をとり、CommandUI 型のデータを返す。ソースは次のようになるが、単に引数を CommandUI のそれぞれのフィールドにふりわけているだけだ。

-- | Make a Command from standard 'GetOpt' options.
makeCommand :: String                         -- ^ name
            -> String                         -- ^ short description
            -> Maybe (String -> String)       -- ^ long description
            -> flags                          -- ^ initial\/empty flags
            -> (ShowOrParseArgs -> [OptionField flags]) -- ^ options
            -> CommandUI flags
makeCommand name shortDesc longDesc defaultFlags options =
  CommandUI {
    commandName         = name,
    commandSynopsis     = shortDesc,
    commandDescription  = longDesc,
    commandUsage        = usage,
    commandDefaultFlags = defaultFlags,
    commandOptions      = options
  }
  where usage pname = "Usage: " ++ pname ++ " " ++ name ++ " [FLAGS]\n\n"
                   ++ "Flags for " ++ name ++ ":"

CommnadUI 型の各フィールドにどのような値が収められているかは installCommand をしれべてみれば分かる。commandName から commandDefaultFlags までは比較的簡単に確認できる。

ghci> commandName installCommand
"install"

ghci> commandSynopsis installCommand
"Copy the files into the install locations. Run register."

ghci> (commandUsage installCommand) "hello"
"Usage: hello install [FLAGS]\n\nFlags for install:"

ghci> (commandUsage installCommand) "cabal"
"Usage: cabal install [FLAGS]\n\nFlags for install:"

ghci> (Data.Maybe.fromJust $ commandDescription installCommand) "cabal"
"Unlike the copy command, install calls the register command.\nIf you want to install into a location that is not what was\nspecified in the configure step, use the copy command.\n"

ghci> commandDefaultFlags installCommand
InstallFlags {installPackageDB = NoFlag, installDistPref = Flag "dist", installUseWrapper = Flag False, installInPlace = Flag False, installVerbosity = Flag Normal}

commandOptions フィールドの値は複雑で次のような型になる。

ghci> :t commandOptions installCommand
commandOptions installCommand
:: ShowOrParseArgs -> [OptionField InstallFlags]

このうち ShowOrParseArgs 型はこの記事の最初のほうで調べた。OptionField は Distribution.Simple.Command で定義されている。

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

OptionField のうち optionName は String 型なので [OptionField installFlags] のリストに含まれる optionName を表示させてみた。

ghci> map optionName $ (commandOptions installCommand) ShowArgs
["verbose","builddir","inplace","shell-wrappers","package-db"]

のこる謎は [OptDesr a] だけになった。

OptDescr 型について調べてみると、Distribution.Simple.Command に定義されているようだ。

ghci> :info OptDescr
data OptDescr a
  = ReqArg Description
           OptFlags
           ArgPlaceHolder
           (Distribution.ReadE.ReadE (a -> a))
           (a -> [String])
  | OptArg Description
           OptFlags
           ArgPlaceHolder
           (Distribution.ReadE.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)
        -- Defined in `Distribution.Simple.Command'

OptDescr 型のコンストラクタは ReqArg、OptArg、CoiceOpt、BoolOpt の4つだ。CommandUI 型を理解するための最後の難関が OptDescr 型のようだが、これはまた別の記事で調べることにする。
[PR]
by tnomura9 | 2014-01-11 16:08 | Haskell | Comments(0)