Фактически, вы использовали вариант государственной монады для передачи случайного генератора за кулисами. Rand
тип в Control.Monad.Random
помогает с этим. API немного запутан, но больше, потому что он является полиморфным по сравнению с типом случайного генератора, который вы используете, чем потому, что он должен быть функциональным. Однако этот дополнительный бит лесов полезен, потому что вы можете легко повторно использовать свой существующий код с помощью разных случайных генераторов, который позволяет тестировать различные алгоритмы, а также явно контролировать, является ли генератор детерминированным (полезно для тестирования) или засеянным внешними данными (в IO
).
Вот простой пример Rand
в действии. RandomGen g =>
в типе подписи говорит нам, что мы можем использовать любой тип генератора случайных чисел. Мы должны явно аннотировать n
как Int
, потому что в противном случае GHC знает только, что он должен быть числовым типом, который может быть сгенерирован и преобразован в строку, которая может быть одной из нескольких возможных опций (например, Double
).
randomReplace :: RandomGen g => String -> Rand g String
randomReplace = foldM go ""
where go str '#' = do
n :: Int <- getRandomR (0, 10)
return (str ++ show n)
go str chr = return $ str ++ [chr]
Для выполнения этого нам нужно, чтобы получить генератор случайных чисел из где-то и передать его в evalRand
. Самый простой способ сделать это, чтобы получить глобальную систему генератор, который мы можем сделать в IO
:
main :: IO()
main = do gen <- getStdGen
print $ evalRand (randomReplace "ab#c#") gen
Это такая общая закономерность, что библиотека предоставляет evalRandIO
функцию, которая делает это для вас:
main :: IO()
main = do res <- evalRandIO $ randomReplace "ab#c#"
print res
В конце концов, код немного более ясен о том, что у него есть генератор случайных чисел и передается его, но по-прежнему достаточно легко следовать. Для более привлекательного кода вы также можете использовать RandT
, что позволяет расширять другие монады (например, IO
) с возможностью генерации случайных значений, позволяя вам отнести все сантехника и настройку к одной части вашего кода.
Так как ваша первая мысль, чтобы перейти к монадам, вам понравится [MonadRandom] (http://hackage.haskell.org/package/MonadRandom). –
«этот генератор должен пройти через весь стек вызовов» - это хорошо. Я хочу _know_, какие функции нечисты. Функция, использующая случайность, не является чистой; Haskell занимается этим, явно передавая генератор случайных чисел вокруг. –