2012-04-23 8 views
11

コマンドラインスイッチに応じて、アルゴリズムAまたはアルゴリズムBのいずれかを使用することができます。または 'debug'フラグが何らかの形で設定されていると、stdoutに冗長な情報を出力するようにしてください。Haskellのグローバルフラグを適切に扱うための適切な方法

どのようにグローバルフラグを実装する必要がありますか?

私は4つのオプションを参照してください、それらのすべては本当に良くありません。

1)IOモナドを必要とし、コア計算関数がすべて純粋なので、関数からのコマンドライン引数を読んでください - 悪いです。私はIOを取得したくありません。

2)main/IOのパラメータを、動作を変更する必要がある 'leaf'関数に渡します。これは、このパラメータを渡すために異なるモジュールの無関係の関数を変更することを意味します。毎回ラッピングコードを変更せずにこのような設定オプションを何度も試してみたいと思います。

3)unsafePerformIOを使用すると、真のグローバル変数を取得できます。このような単純な問題のために、面倒で不慣れだと感じます。

4)機能の真ん中には、両方のオプションのコードがあり、そのうちの1つをコメントアウトします。あるいは、関数do_stuff_Aとdo_stuff_Bを持っていて、どの関数が 'needDebugInfo = True'というグローバル関数に応じて呼び出されるかを変更します。これは私がdebuginfoのためにやっていることですが、コンパイルなしで変更することはできません。実際には利用可能な最良の方法ではありません...

私はグローバルな変更が必要ない状態 - 私は実行時に不変の単純なグローバルフラグを持っているが、プログラムの起動時に何とか設定することができます。オプションはありますか?

答えて

3

私たちの新しいHFlagsライブラリは、このために正確です。

あなたはこのに見て、あなたの例のような使用例を参照したい場合:

https://github.com/errge/hflags/blob/master/examples/ImportExample.hs

https://github.com/errge/hflags/blob/master/examples/X/B.hs

https://github.com/errge/hflags/blob/master/examples/X/Y_Y/A.hs

パラメータ渡しのいかなる種類のモジュール間で必要とされていません簡単な構文で新しいフラグを定義することができます。それはunsafePerformIOを内部的に使用していますが、安全な方法でそれを行うと考えています。あなたはそれを心配する必要はありません。別のオプションはGHC implicit parametersあるhttp://blog.risko.hu/2012/04/ann-hflags-0.html

14

最近では、を使用して、アプリケーションの読み取り専用状態を構成することを推奨します。環境は起動時に初期化され、プログラムの最上位レベルで利用可能です。

is xmonad

newtype X a = X (ReaderT XConf (StateT XState IO) a) 
    deriving (Functor, Monad, MonadIO, MonadReader XConf) 

X代わりにIOで実行するプログラムの最上位の部品; XConfは、コマンドラインフラグ(および環境変数)によって初期化されたデータ構造です。

XConfの状態は、それを必要とする関数に純粋なデータとして渡すことができます。 newtypeを導出すると、状態にアクセスするためのMonadReaderコードをすべて再利用できるようになります。

このアプローチでは、意味の純度は2になりますが、モナドが配管を行うので、書くコードが少なくなります。

私はその "真の"ハスケルの方法は読み取り専用の設定状態を行うと思います。

-

グローバル状態を初期化するためにunsafePerformIOを使用するアプローチも、もちろん、動作しますが、(あなたはあなたのプログラムが同時または並列にするなどの場合)、最終的にあなたを噛まないように傾向があります。彼らはまたfunny initialization semanticsを持っています。

9

モナドReaderを使用すると、どこでもパラメータを渡すのと同じ効果を得ることができます。アプリケーションスタイルは、オーバーヘッドを通常の機能コードに比べてかなり低くすることができますが、それでもなおかなり厄介なことがあります。これはコンフィギュレーションの問題に対する最も一般的な解決策ですが、私はそれを非常に満足のいくものにしません。実際には、パラメータを明示的に渡すことはしばしば醜いです。

代替方法はreflectionパッケージです。このような一般的な構成データをtypeclassコンテキストで渡すことができます。つまり、コードだけで追加の値を変更する必要がなく、タイプのみを変更する必要がありません。基本的には、プログラム内のすべての入力/結果型に新しい型パラメータを追加して、特定の構成のコンテキスト内で動作するすべての型が、その型の構成に対応する型を持つようにします。このタイプは、複数の設定を使用して誤って値を混ぜるのをやめ、実行時に関連する設定にアクセスできるようにします。

これにより、アプリケーションスタイルのすべてを安全な状態で書き込み、複数の設定を混在させることができます。それは聞こえるよりずっと簡単です。ここにはan exampleがあります。

(フルdiscloure:。私はリフレクションパッケージに働いてきた)

2

でこのようなものについてのブログ記事があります。これらはあなたの選択肢(2)の苦痛を軽減します:中間型の署名が感染しますが、中間コードを変更する必要はありません。

はここでの例です:あなたは、引数Trueでこのプログラムを実行する場合

{-# LANGUAGE ImplicitParams #-} 
import System.Environment (getArgs)  

-- Put the flags in a record so you can add new flags later 
-- without affecting existing type signatures. 
data Flags = Flags { flag :: Bool } 

-- Leaf functions that read the flags need the implicit argument 
-- constraint '(?flags::Flags)'. This is reasonable. 
leafFunction :: (?flags::Flags) => String 
leafFunction = if flag ?flags then "do_stuff_A" else "do_stuff_B" 

-- Implicit argument constraints are propagated to callers, so 
-- intermediate functions also need the implicit argument 
-- constraint. This is annoying. 
intermediateFunction :: (?flags::Flags) => String 
intermediateFunction = "We are going to " ++ leafFunction 

-- Implicit arguments can be bound at the top level, say after 
-- parsing command line arguments or a configuration file. 
main :: IO() 
main = do 
    -- Read the flag value from the command line. 
    commandLineFlag <- (read . head) `fmap` getArgs 
    -- Bind the implicit argument. 
    let ?flags = Flags { flag = commandLineFlag } 
    -- Subsequent code has access to the bound implicit. 
    print intermediateFunction 

それはWe are going to do_stuff_Aを出力します。引数がFalseの場合、We are going to do_stuff_Bが出力されます。

このアプローチはthe reflection package mentioned in another answerと似ていると思いますが、HFlags mentioned in the accepted answerがおそらく良い選択だと思いますが、私はこの回答を完全に追加しています。

関連する問題