2

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

该函数是否需要随机生成器作为其参数之一?如果是这样,该生成器是否必须通过整个调用堆栈?还有其他可能的方法吗?我应该在这里使用State单子吗?我将不胜感激一个展示可行方法的玩具示例...

4

2 回答 2

2

实际上,您将使用 state monad 的变体在幕后传递随机生成器。输入的Rand类型Control.Monad.Random对此有所帮助。API 有点令人困惑,但更多的是因为它在您使用的随机生成器类型上是多态的,而不是因为它必须是功能性的。然而,这个额外的脚手架很有用,因为您可以轻松地使用不同的随机生成器重用现有代码,这使您可以测试不同的算法以及显式控制生成器是确定性的(有利于测试)还是使用外部数据播种(在IO)。

这是一个简单的例子Rand。类型签名中的RandomGen g =>告诉我们,我们可以为它使用任何类型的随机生成器。我们必须明确注释n为 an 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),使其具有生成随机值的能力,让您将所有管道和设置委托给代码的一部分。

于 2015-03-05T22:04:31.907 回答
0

这只是一个单子映射

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
于 2015-03-05T22:44:29.523 回答