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
渡す必要がなくなったことに注意してください。モナドはバックグラウンドで自動的にそれを行います。config
helper
Reader
Reader
モナドはこれを行うために副作用を使用しないことを強調することが重要です。Reader
モナドは、最初の例で前に行ったのと同じ方法でパラメーターを手動で渡すだけの、内部の純粋関数に変換されます。このプロセスを自動化するだけなので、実行する必要はありません。
Haskellを初めて使用する場合は、パラメータの受け渡しを使用して情報を移動する方法を学習するための最初のアプローチをお勧めします。モナドがどのように機能し、どのようにパラメーターの受け渡しを自動化するかを理解している場合にのみReader
モナドを使用します。そうしないと、問題が発生した場合に修正する方法がわかりません。
IORef
グローバル変数を渡すためのアプローチとしてsについて言及しなかったのはなぜか疑問に思われるかもしれません。その理由はIORef
、変数を保持するための参照を定義した場合でもIORef
、ダウンストリーム関数がそれにアクセスできるようにするためにそれ自体を渡す必要があるため、 IORef
sを使用しても何も得られないためです。主流の言語とは異なり、Haskellは、通常のパラメーターであるかどうかに関係なく、すべての関数に情報の取得元を宣言させます。
foo :: Config -> ...
...またはReader
モナドとして:
bar :: Reader Config ...
...または変更可能な参照として:
baz :: IORef Config -> IO ...
これは良いことです。なぜなら、関数をいつでも検査して、関数が利用できる情報、さらに重要なことに、関数が利用できない情報を理解できるからです。これにより、関数のタイプによって、関数が依存するすべてのものが常に明示的に定義されるため、関数のデバッグが容易になります。