Haskell で (PRNG のように) 隠れた方法で状態を初期化するにはどうすればよいですか?
-
13-09-2019 - |
質問
State モナドのチュートリアルをいくつか読んで、アイデアは理解できたと思います。
たとえば、次のようになります この素晴らしいチュートリアル:
import Data.Word
type LCGState = Word32
lcg :: LCGState -> (Integer, LCGState)
lcg s0 = (output, s1)
where s1 = 1103515245 * s0 + 12345
output = fromIntegral s1 * 2^16 `div` 2^32
getRandom :: State LCGState Integer
getRandom = get >>= \s0 -> let (x,s1) = lcg s0
in put s1 >> return x
OK、それでは getRandom を使うことができます。
*Main> runState getRandom 0
(0,12345)
*Main> runState getRandom 0
(0,12345)
*Main> runState getRandom 1
(16838,1103527590)
ただし、PRNG を呼び出すたびにシードを PRNG に渡す必要があります。Haskellの実装で利用可能なPRNGにはそれが必要ではないことは知っています。
Prelude> :module Random
Prelude Random> randomRIO (1,6 :: Int)
(...) -- GHC prints some stuff here
6
Prelude Random> randomRIO (1,6 :: Int)
1
ですから、ほとんどのチュートリアルで見ることができるものは「永続的な」状態ではなく、状態を通す便利な方法であるように見えるので、私はおそらく州のモナドを誤解しました。
それで...自動的に初期化される状態を持つにはどうすればいいですか(いくつかの 時間やその他のあまり予測できないデータを使用する関数)、Random モジュールなど は。
どうもありがとう!
解決
randomRIO
を使用します IO
モナド。インタプリタは IO
モナド。それがあなたの例で見られるものです。実際には、コードのトップレベルでそれを行うことはできません。とにかく、すべてのモナドと同様に、それを do 式に入れる必要があります。
一般的なコードでは、IO モナドは避けるべきです。コードで IO モナドを使用すると、外部状態に永久に結び付けられるため、そこから抜け出すことができなくなります。IO モナドを使用するコードがある場合、それを呼び出すコードも IO モナドを使用する必要があります。そこから「抜け出す」安全な方法はありません)。それで、 IO
モナドは外部環境へのアクセスなど、絶対に必要な場合にのみ使用してください。
ローカルの自己完結型状態などの場合は、IO モナドを使用しないでください。使用できます State
あなたが述べたようにモナド、またはを使用することもできます ST
モナド。ST モナドには、IO モナドと同じ機能が多数含まれています。つまりがある STRef
可変細胞、類似 IORef
. 。そして、IO と比較した ST の良い点は、完了したら次の呼び出しができることです。 runST
ST モナドで計算結果をモナドから取得しますが、これは IO では実行できません。
状態を「隠す」ことに関しては、それはモナドの Haskell の do 式の構文の一部として提供されます。明示的に状態を渡す必要があると考えている場合は、モナド構文を正しく使用していません。
IO モナドで IORef を使用するコードは次のとおりです。
import Data.IORef
foo :: IO Int -- this is stuck in the IO monad forever
foo = do x <- newIORef 1
modifyIORef x (+ 2)
readIORef x
-- foo is an IO computation that returns 3
ST モナドを使用するコードは次のとおりです。
import Control.Monad.ST
import Data.STRef
bar :: Int
bar = runST (do x <- newSTRef 1
modifySTRef x (+ 2)
readSTRef x)
-- bar == 3
コードの単純さは本質的に同じです。ただし、後者の場合はモナドから値を取得できますが、前者の場合は別の IO 計算の中に値を入れないと取得できません。
他のヒント
secretStateValue :: IORef SomeType
secretStateValue = unsafePerformIO $ newIORef initialState
{-# NOINLINE secretStateValue #-}
次に、通常のメソッドで SecretStateValue にアクセスします。 readIORef と writeIORef, 、IOモナド内。
そのため、私はおそらく State モナドを誤解していました。ほとんどのチュートリアルで見られるものは「永続的な」状態ではなく、単にスレッド状態を取得する便利な方法であるように見えるからです。
状態モナドはまさに、あるスコープを介して状態をスレッド化することに関するものです。
トップレベルの状態が必要な場合、それは言語の外にあります (そして、グローバル可変変数を使用する必要があります)。これにより、コードのスレッド セーフがどのように複雑になるかに注意してください。その状態はどのように初期化されるのでしょうか?そしていつ?そして、どのスレッドによってですか?