嵌套纯函数中的随机性

时间:2015-03-05 21:30:10

标签: haskell functional-programming monads purely-functional

我想提供一个函数,用一个不同的随机数替换字符串中每次出现的#。在非纯语言中,它是微不足道的。但是,它应该如何用纯语言设计?我不想使用unsafePerformIO,因为它看起来像是黑客而不是正确的设计。

此功能是否需要随机发生器作为其参数之一?如果是这样,那个生成器是否必须通过整个调用堆栈?还有其他可能的方法吗?我应该使用State monad吗?我要感谢一个展示可行方法的玩具示例......

2 个答案:

答案 0 :(得分:2)

事实上,你会使用状态monad的变体在幕后传递随机生成器。 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,它允许您扩展其他monad(如IO),并能够生成随机值,让您将所有管道和设置放到一部分你的代码。

答案 1 :(得分:0)

它只是一个monadic映射

import Control.Applicative
import Control.Monad.Random
import Data.Char

randomReplace :: RandomGen g => String -> Rand g String
randomReplace = mapM f where
    f '#' = intToDigit <$> getRandomR (0, 10)
    f  c  = return c

main = evalRandIO (randomReplace "#abc#def#") >>= print