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