4

Haskellに状態を保存するタスクのデザインパターンは何ですか?たとえば、設定ファイルの読み取りとメモリへの設定パラメータの保存を提供するhaskellを使用してライブラリを作成したいと思います。

例えば:

構成付きのファイルがあります。現在、設定ファイルの構文は重要ではありません。私は設定ファイルを読み、それをいくつかのhaskellデータ構造に解析します。次に、このライブラリを使用してconfigからパラメータを取得するプログラムを終了します。haskellにはグローバル変数はありません。毎回設定を読み取って解析する関数を呼び出したくありません。configを1回読み取り、paramsを何度も取得したい。

haskellのこれらのタイプの問題の一般的な慣行は何ですか?

ありがとうございました。

4

2 に答える 2

13

2つの解決策があり、サンプルプログラムを使用して両方をデモンストレーションします。例として、次の簡単な構成ファイルを使用してみましょう。

-- config.hs

data Config = Config { firstName :: String, lastName :: String }
    deriving (Read, Show)

ghciそれをロードして、簡単なサンプルファイルを生成しましょう。

$ ghci config.hs
>>> let config = Config "Gabriel" "Gonzalez"
>>> config
Config {firstName = "Gabriel", lastName = "Gonzalez"}
>>> writeFile "config.txt" config
>>> ^D

次に、この構成ファイルを読み込んで出力するプログラムを定義しましょう。

-- config.hs
data Config = Config { firstName :: String, lastName :: String }
    deriving (Read, Show)

main = do
    config <- fmap read $ readFile "config.txt" :: IO Config
    print config

それが機能することを確認しましょう:

$ runhaskell config.hs
Config {firstName = "Gabriel", lastName = "Gonzalez"}

それでは、プログラムを変更して、不自然な方法ではありますが、名前をきれいに印刷してみましょう。次のプログラムは、構成を渡すための最初のアプローチを示しています。構成を必要とする関数に通常のパラメーターとして構成を渡します。

-- config.hs
data Config = Config { firstName :: String, lastName :: String }
    deriving (Read, Show)

main = do
    config <- fmap read $ readFile "config.txt" :: IO Config
    putStrLn $ pretty config

pretty :: Config -> String
pretty config = firstName config ++ helper config

helper :: Config -> String
helper config = " " ++ lastName config

これは最も軽量なアプローチです。ただし、非常に大規模なプログラムでは、手動でパラメータを渡すすべてが面倒になる場合があります。幸いなことに、モナドと呼ばれる、パラメーターの受け渡しを処理するモナドがありますReader。変数などの「環境」を指定するconfigと、その環境が読み取り専用変数として渡され、Readerモナド内のすべての関数がアクセスできるようになります。

次のプログラムは、Readerモナドの使用方法を示しています。

-- config.hs

import Control.Monad.Trans.Reader -- from the "transformers" package

data Config = Config { firstName :: String, lastName :: String }
    deriving (Read, Show)

main = do
    config <- fmap read $ readFile "config.txt" :: IO Config
    putStrLn $ runReader pretty config

pretty :: Reader Config String
pretty = do
    name1 <- asks firstName
    rest  <- helper
    return (name1 ++ rest)

helper :: Reader Config String
helper = do
    name2 <- asks lastName
    return (" " ++ name2)

configを呼び出す時点で変数を1回だけ渡す方法に注意してください。runReaderそのルーチン内のすべての関数は、askまたはasks関数のいずれかを使用して、読み取り専用のグローバル変数のように変数にアクセスできます。同様に、をpretty呼び出すときに、パラメータとしてhelper渡す必要がなくなったことに注意してください。モナドはバックグラウンドで自動的にそれを行います。confighelperReader

Readerモナドはこれを行うために副作用を使用しないことを強調することが重要です。Readerモナドは、最初の例で前に行ったのと同じ方法でパラメーターを手動で渡すだけの、内部の純粋関数に変換されます。このプロセスを自動化するだけなので、実行する必要はありません。

Haskellを初めて使用する場合は、パラメータの受け渡しを使用して情報を移動する方法を学習するための最初のアプローチをお勧めします。モナドがどのように機能し、どのようにパラメーターの受け渡しを自動化するかを理解している場合にのみReaderモナドを使用します。そうしないと、問題が発生した場合に修正する方法がわかりません。

IORefグローバル変数を渡すためのアプローチとしてsについて言及しなかったのはなぜか疑問に思われるかもしれません。その理由はIORef、変数を保持するための参照を定義した場合でもIORef、ダウンストリーム関数がそれにアクセスできるようにするためにそれ自体を渡す必要があるため、 IORefsを使用しても何も得られないためです。主流の言語とは異なり、Haskellは、通常のパラメーターであるかどうかに関係なく、すべての関数に情報の取得元を宣言させます。

foo :: Config -> ...

...またはReaderモナドとして:

bar :: Reader Config ...

...または変更可能な参照として:

baz :: IORef Config -> IO ...

これは良いことです。なぜなら、関数をいつでも検査して、関数が利用できる情報、さらに重要なことに、関数が利用できない情報を理解できるからです。これにより、関数のタイプによって、関数が依存するすべてのものが常に明示的に定義されるため、関数のデバッグが容易になります。

于 2012-12-25T21:51:26.217 に答える
2

さて、1つの練習はconfiguratorで使用されるものかもしれません。これを使用する方法の概要は、ブログ投稿で取り上げられました。実装を詳しく調べて、プロジェクトで何が機能するかを確認できます。

于 2012-12-25T12:58:28.693 に答える