4

私は 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

ほとんどのチュートリアルで見られるのは「永続的な」状態ではなく、状態をスレッド化するための便利な方法であるように見えるため、おそらく State モナドを誤解していました。

それで... Random モジュールのように、自動的に初期化される状態 (時間やその他のあまり予測できないデータを使用する関数から可能) を取得するにはどうすればよいでしょうか?

どうもありがとう!

4

3 に答える 3

6

randomRIOIOモナドを使用します。IOインタープリターはモナドでも機能するため、これはインタープリターでうまく機能するようです。それがあなたの例で見ているものです。コードのトップレベルで実際にそれを行うことはできません.とにかく、すべてのモナドと同様に、それをdo式に入れる必要があります.

一般的なコードでは、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 計算の中に入れないと取得できません。

于 2009-07-13T21:12:16.697 に答える
4

したがって、ほとんどのチュートリアルで見られるものは「永続的な」状態ではなく、状態をスレッド化するための便利な方法であるように見えるため、おそらく状態モナドを誤解しました。

状態モナドは、正確には、あるスコープを介して状態をスレッド化することに関するものです。

トップレベルの状態が必要な場合、それは言語の範囲外です(そして、グローバルな可変変数を使用する必要があります)。これにより、コードのスレッドセーフが複雑になる可能性があることに注意してください。その状態はどのように初期化されますか?そしていつ?そして、どのスレッドで?

于 2009-07-13T20:08:43.843 に答える
4
secretStateValue :: IORef SomeType
secretStateValue = unsafePerformIO $ newIORef initialState
{-# NOINLINE secretStateValue #-}

IO モナドで通常のreadIORef と writeIORefを使用して secretStateValue にアクセスします。

于 2009-07-13T18:50:26.670 に答える