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

System.Console.GetOpt その3

前へ 目次 次へ

参照: cabal-instsll 関連のリンクSystem.Console.GetOpt

1. System.Console.GetOpt のプログラム例 Opts2.hs の概要

getOpt の使い方のプログラム例で Opts1.hs では、オプションを Options 型の代数的データ型のそれぞれのコンストラクタに分けて定義していた。したがって、プログラム例での complilerOpts のコマンドラインオプションの情報は次のように、Options 型のリストとして与えられる。

Prelude> :l Opts1.hs
[1 of 1] Compiling Opts1 ( Opts1.hs, interpreted )
Ok, modules loaded: Opts1.
*Opts1> compilerOpts ["-v","--libdir","/var/lib"]
([Verbose,LibDir "/var/lib"],[])

しかし、System.Console.GetOpt モジュールのハドック文書にはもうひとつのプログラム例 Opts2.hs が紹介してある。そのプログラムでは、コマンド・ライン情報のオプションの扱い方の別のやり方としてコマンドラインオプションのフラグ情報を、Options 型のフィールド情報として保持する方法がとられている。

Opts2.hs の詳細については後述するが、上の Opts1.hs で処理したコマンドラインオプションのパースの結果は Opts2 モジュールでは次のようになる。

*Opts1> :l Opts2.hs
[1 of 1] Compiling Opts2 ( Opts2.hs, interpreted )
Ok, modules loaded: Opts2.
*Opts2> compilerOpts ["-v","--libdir","/var/lib"]
(Options {optVerbose = True, optShowVersion = False, optOutput = Nothing, optInput = Nothing, optLibDirs = ["/var/lib"]},[])

comlilerOpts の戻値をみると、コマンド・ラインオプションのパース結果は、オプション型のリストではなく、一個のOptions 型の optVerbous フィールの値が True になり、optLibDirs フィールドの値が ["/var/lib"] のようなパス名のリストであるものとして戻されている。

2. 代数的データ型のフィールドにオプションのデータを保持するときの注意点

これをみると分かるように Opts2.hs のプログラムのほうが、コマンド・ラインオプション全体の情報を1個のオプション型の値としてまとめて取り扱うことができる。

こういうやり方は、手続き型のプログラムでは Options 型の構造体のフィールドに値を設定するだけなので普通に頻用されている。しかし、Haskell のような手続き型のプログラム言語では変数の書き換えは許されない。

変数の書き換えが許されないのは、プログラムに状態を持ち込まないことでランタイムのエラーを排除できるが、実際にはこの例のように状態がないとプログラムが記述できない場合が多い。

このような問題の解決法としてまず思いつくのが State モナドを利用することだが、Opts2.hs で取られている戦略は異なる。

そこでは、パースを行った時にアクションとして Options -> Options 型の関数を渡すことで Options 型データのフィールドの値を書き換えて、新しい Options 型のデータを作ることができるようにしている。

パースの際に戻された Options -> Options 型の関数を数珠つなぎにデフォールトの Options 型のデータに関数適用していけば、上の Opts2.hs の実行で見られたようなオプションの情報を保持した Options 型のデータを得ることができる。

なんとも面倒なやり方だが、参照透明性を堅持したまま、状態のあるプログラムを作るためにはそれなりの工夫がいるということだ。

3. Opts2.hs のコード

Opts2.hs のプログラムは次のようになっている。

module Opts2 where

import System.Console.GetOpt
import Data.Maybe ( fromMaybe )

data Options = Options
{ optVerbose     :: Bool
, optShowVersion :: Bool
, optOutput      :: Maybe FilePath
, optInput       :: Maybe FilePath
, optLibDirs     :: [FilePath]
} deriving Show

defaultOptions    = Options
{ optVerbose     = False
, optShowVersion = False
, optOutput      = Nothing
, optInput       = Nothing
, optLibDirs     = []
}

options :: [OptDescr (Options -> Options)]
options =
[ Option ['v']     ["verbose"]
     (NoArg (\ opts -> opts { optVerbose = True }))
     "chatty output on stderr"
, Option ['V','?'] ["version"]
     (NoArg (\ opts -> opts { optShowVersion = True }))
     "show version number"
, Option ['o']     ["output"]
     (OptArg ((\ f opts -> opts { optOutput = Just f }) . fromMaybe "output")
             "FILE")
     "output FILE"
, Option ['c']     []
     (OptArg ((\ f opts -> opts { optInput = Just f }) . fromMaybe "input")
             "FILE")
     "input FILE"
, Option ['L']     ["libdir"]
     (ReqArg (\ d opts -> opts { optLibDirs = optLibDirs opts ++ [d] }) "DIR")
     "library directory"
]

compilerOpts :: [String] -> IO (Options, [String])
compilerOpts argv =
   case getOpt Permute options argv of
      (o,n,[]  ) -> return (foldl (flip id) defaultOptions o, n)
      (_,_,errs) -> ioError (userError (concat errs ++ usageInfo header options))
  where header = "Usage: ic [OPTION...] files..."

上のプログラムで、data Options で定義されているのがオプションを扱うデータ型 Options だ。Options 型のフィールド名は、optVerbose, optShowVersion, optOutput, optInput, optLibDirs, の5つだ。また、デフォールトの Options 型の値として defaultOptions が定義されている。各フィールドの値を変更は defalultOptions に フィールドのアクセサを関してアクセする事で行われる。例えば、optVerbose フィールドの値を True に設定するには次のようにする。

*Opts2> defaultOptions {optVerbose = True}
Options {optVerbose = True, optShowVersion = False, optOutput = Nothing, optInput = Nothing, optLibDirs = []}

Options 型のフィールドの操作を行う関数は、コマンドラインオプションのデータベース options の中で利用されている。例えば、上の optVerbose フィールドを True にする関数は、OptDescr (Options -> Options) 型の第3フィールドに納められており、

(NoArg (\ opts -> opts { optVerbose = True }))

である。NoArg は ArgDescr a 型のデータのコンストラクタのひとつだ。ArgDescr a 型は System.Console.GetOpt 型で定義されているが、前回の記事で述べた。

cabal-install のソースコードの Distribution.Client.Setup モジュールで定義されている globalCommand の commandOptions フィールドには次のような記載がある。

globalVersion (\v flags -> flags { globalVersion = v })

従って、cabal-install のコマンドラインオプションのパースには、上の Opts2.hs のやり方でオプション・フラグの値を操作しているようだ。
by tnomura9 | 2013-12-29 10:55 | Haskell | Comments(0)
<< Text.ParserComb... 右近ライブ その2 >>