2009-07-13 6 views
4

Я просмотрел некоторые учебники по государственной монаде, и я думаю, что у меня есть идея.Как я могу инициализировать состояние скрытым способом в Haskell (как это делает PRNG)?

Например, как в this nice tutorial:

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) 

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

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

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

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

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

ответ

6

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

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

Для таких вещей, как местное автономное состояние, вы не должны использовать монаду IO. Вы можете использовать монаду State, как вы упомянули, или вы можете использовать монаду ST. Монада ST содержит множество тех же функций, что и монада IO; т.е. существует изменяемых клеток, аналогично 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 

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

+0

Спасибо! Это было очень полезно. Что касается вашего комментария, «в первом мы не можем не помещать его в другое вычисление ввода-вывода» - я полагаю, поэтому монада IO должна быть самой внутренней, когда вы объединяете несколько монадов (используя монадные трансформаторы)? – Jay

+4

Теперь, когда я думаю об этом, PRNG обязательно будет использовать монашу IO, верно? Он нуждается в энтропии, поэтому ему нужны данные из «где-то». Это на самом деле * нуждается в * по крайней мере одной функции, которая не является ссылочной прозрачностью (в противном случае это было бы не полезно, например, для криптографического кода)! :-) – Jay

4
secretStateValue :: IORef SomeType 
secretStateValue = unsafePerformIO $ newIORef initialState 
{-# NOINLINE secretStateValue #-} 

Теперь получить доступ к secretStateValue с нормальным readIORef and writeIORef, в монаде IO.

+2

Зло, но это тот же трюк, что и стандартный модуль 'Random' ... – ephemient

+1

Говоря о том, что' Random' использует 'atomicModifyIORef' вместо пары' readIORef' + 'writeIORef'; это кажется хорошей идеей. – ephemient

+2

Действительно, если есть доступ из нескольких потоков, используя atomModifyIORef, это лучшая идея. – bdonlan

4

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

Государственная монада - это точно о потоковом состоянии через некоторый объем.

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