14

多くの場合、多くの場所で使用されているコア関数を何らかの方法で構成可能にする必要があります。つまり、コマンドラインスイッチに応じてアルゴリズムAまたはアルゴリズムBのいずれかを使用できます。または、「debug」フラグが何らかの方法で設定されている場合は、余分な情報をstdoutに出力します。

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

4つのオプションがありますが、どれもあまり良くありません。

  1. 関数からコマンドライン引数を読み取ります-悪いです。これにはIOモナドが必要であり、コア計算関数はすべて純粋であるため、そこにIOを入れたくありません。

  2. main / IOから、動作を変更する必要のある「leaf」関数にパラメーターを渡します。これは、このパラメーターを渡すために、異なるモジュール内の多数の無関係な関数を変更することを意味するため、完全に使用できません。そのようなものを試してみたいと思います。毎回ラッピングコードを変更せずに、構成オプションを複数回。

  3. unsafePerformIO真のグローバル変数を取得するために使用します-このような単純な問題に対しては醜くてやり過ぎだと感じます。

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

グローバルな可変状態は必要ないか、必要ありません-実行時に不変であるが、プログラムの起動時に何らかの方法で設定できる単純なグローバルフラグが必要です。オプションはありますか?

4

4 に答える 4

16

最近ではReaderモナドを使用してアプリケーションの読み取り専用状態を構成することを好みます。環境は起動時に初期化され、プログラムのトップレベル全体で利用できます。

はxmonadです:

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

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

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

このアプローチは、2のセマンティック純度を保持しますが、モナドが配管を行うため、記述するコードが少なくなります。

読み取り専用の設定状態を行うための「真の」Haskellの方法だと思います。

-

unsafePerformIOもちろん、グローバル状態を初期化するために使用するアプローチも機能しますが、最終的には(たとえば、プログラムを並行または並列にする場合に)噛み付く傾向があります。また、面白い初期化セマンティクスがあります。

于 2012-04-23T17:50:10.450 に答える
10

モナドを使用して、Readerあらゆる場所にパラメーターを渡すのと同じ効果を得ることができます。適用可能なスタイルは、通常の関数型コードと比較してオーバーヘッドをかなり低くすることができますが、それでもかなり厄介なことがあります。これは構成の問題に対する最も一般的な解決策ですが、私はそれがひどく満足できるものではないと思います。実際、パラメータを明示的に渡すことは、多くの場合、それほど醜いものではありません。

別の方法はリフレクションパッケージです。これを使用すると、このような一般的な構成データを型クラスコンテキストを介して渡すことができます。つまり、コードを変更して追加の値を取得する必要はなく、型のみを渡すことができます。基本的に、プログラム内のすべての入力/結果タイプに新しいタイプパラメーターを追加して、特定の構成のコンテキスト内で動作するすべてのものが、そのタイプのその構成に対応するタイプを持つようにします。このタイプは、複数の構成を使用して誤って値を混合することを防ぎ、実行時に関連する構成にアクセスできるようにします。

これにより、安全でありながら、すべてを適用可能なスタイルで記述するオーバーヘッドが回避され、複数の構成を混在させることができます。思ったよりずっと簡単です。これがです。

(完全な開示:私はリフレクションパッケージに取り組んできました。)

于 2012-04-23T17:50:13.653 に答える
4

私たちの新しい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を使用していますが、安全な方法で使用していると考えており、心配する必要はありません。

このようなものについてのブログ投稿があります:http://blog.risko.hu/2012/04/ann-hflags-0.html

于 2012-05-03T06:30:56.737 に答える
2

もう1つのオプションは、GHCの暗黙的なパラメーターです。これらはあなたのオプションのより苦痛の少ないバージョンを提供します(2):中間型の署名は感染しますが、中間コードを変更する必要はありません。

次に例を示します。

{-# 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

このプログラムを引数付きで実行すると、次のTrueように出力されWe are going to do_stuff_Aます。引数を指定Falseすると、が出力されWe are going to do_stuff_Bます。

このアプローチは、別の回答で言及されているリフレクションパッケージに似ていると思います。受け入れられた回答で言及されているHFlagsの方がおそらく良い選択だと思いますが、完全を期すためにこの回答を追加します。

于 2013-10-05T01:03:45.543 に答える