Как я могу инициализировать состояние скрытно в Haskell (как это делает PRNG)? - PullRequest
4 голосов
/ 13 июля 2009

Я прошел некоторые уроки по монаде Государства, и я думаю, что понял.

Например, как в этом хорошем учебнике :

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

ОК, так что я могу использовать getRandom:

*Main> runState getRandom 0
(0,12345)
*Main> runState getRandom 0
(0,12345)
*Main> runState getRandom 1              
(16838,1103527590)

Но мне все еще нужно передавать семена в PRNG каждый раз, когда я это называю. Я знаю что PRNG, доступный в реализациях Haskell, не нуждается в следующем:

Prelude> :module Random
Prelude Random> randomRIO (1,6 :: Int)
(...) -- GHC prints some stuff here
6
Prelude Random> randomRIO (1,6 :: Int)
1

Так что я, вероятно, неправильно понял Государственную монаду, потому что то, что я видел в большинстве уроков не кажется "постоянным" состоянием, а просто удобным способом для обработки потоков.

Итак ... Как я могу иметь состояние, которое автоматически инициализируется (возможно из некоторых функция, которая использует время и другие не очень предсказуемые данные), например, модуль Random делает

Большое спасибо!

Ответы [ 3 ]

6 голосов
/ 14 июля 2009

randomRIO использует монаду IO. Похоже, это хорошо работает в интерпретаторе, потому что интерпретатор также работает в монаде IO. Это то, что вы видите в своем примере; на самом деле вы не можете сделать это на верхнем уровне в коде - вам все равно придется поместить это в выражение-выражение, как все монады.

В общем коде вы должны избегать монады ввода-вывода, потому что, как только ваш код использует монаду ввода-вывода, он навсегда привязан к внешнему состоянию - вы не можете выйти из него (т.е. если у вас есть код, который использует монаду ввода-вывода) любой код, который вызывает его, также должен использовать монаду ввода-вывода; не существует безопасного способа «выбраться» из него). Поэтому монаду IO следует использовать только для таких вещей, как доступ к внешней среде, где она абсолютно необходима.

Для таких вещей, как локальное автономное состояние, вы не должны использовать монаду IO. Вы можете использовать монаду State, как вы упоминали, или можете использовать монаду ST. Монада ST содержит много тех же функций, что и монада IO; то есть есть STRef изменчивые клетки, аналогичные IORef. И хорошая вещь в ST по сравнению с IO состоит в том, что когда вы закончите, вы можете вызвать runST для монады ST, чтобы получить результат вычисления из монады, чего вы не можете сделать с IO.

Что касается «сокрытия» состояния, которое входит в состав синтаксиса do-выражений в Haskell для монад. Если вы считаете, что вам нужно явно передать состояние, значит, вы неправильно используете синтаксис монады.

Вот код, который использует IORef в монаде IO:

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

Простота кода практически одинакова; за исключением того, что в последнем случае мы можем получить значение из монады, а в первом мы не можем без помещения его в другое вычисление ввода-вывода.

4 голосов
/ 14 июля 2009

Так что я, вероятно, неправильно понял монаду State, потому что то, что я мог видеть в большинстве уроков, кажется не «постоянным» состоянием, а просто удобным способом для обработки потоков.

Монада состояний как раз о потоке состояния через некоторую область.

Если вам нужно состояние верхнего уровня, это за пределами языка (и вам придется использовать глобальную изменяемую переменную). Обратите внимание, как это, вероятно, усложнит потокобезопасность вашего кода - как это состояние инициализируется? и когда? И какой нитью?

4 голосов
/ 13 июля 2009
secretStateValue :: IORef SomeType
secretStateValue = unsafePerformIO $ newIORef initialState
{-# NOINLINE secretStateValue #-}

Теперь получите доступ к вашему secretStateValue с помощью обычного readIORef и writeIORef в монаде ввода / вывода.

...