嵌套纯函数中的随机性
Randomness in a nested pure function
我想提供一个函数,用不同的随机数替换字符串中每次出现的 #
。在非纯语言中,这是微不足道的。但是,纯语言应该如何设计呢?我不想使用 unsafePerformIO
,因为它看起来像是一个 hack 而不是正确的设计。
此函数是否需要随机生成器作为其参数之一?如果是这样,该生成器是否必须通过整个调用堆栈传递?还有其他可能的方法吗?我应该在这里使用 State
monad 吗?我将不胜感激展示可行方法的玩具示例...
事实上,您可以使用状态 monad 的变体在幕后传递随机生成器。 Control.Monad.Random
中的 Rand
类型有助于此。 API 有点令人困惑,但更多的是因为它在您使用的随机生成器类型上是多态的,而不是因为它必须是函数式的。然而,这种额外的脚手架很有用,因为您可以轻松地使用不同的随机生成器重用现有代码,这使您可以测试不同的算法并明确控制生成器是确定性的(有利于测试)还是使用外部数据播种(在IO
).
这是 Rand
的一个简单示例。类型签名中的 RandomGen g =>
告诉我们可以使用 any 类型的随机生成器。我们必须明确地将 n
注释为 Int
因为否则 GHC 只知道它必须是 some 可以生成并转换为字符串的数字类型,这可以是多个可能选项之一(如 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
),并具有生成随机值的能力,让您将所有管道和设置归入你的代码。
这只是一个单子映射
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
我想提供一个函数,用不同的随机数替换字符串中每次出现的 #
。在非纯语言中,这是微不足道的。但是,纯语言应该如何设计呢?我不想使用 unsafePerformIO
,因为它看起来像是一个 hack 而不是正确的设计。
此函数是否需要随机生成器作为其参数之一?如果是这样,该生成器是否必须通过整个调用堆栈传递?还有其他可能的方法吗?我应该在这里使用 State
monad 吗?我将不胜感激展示可行方法的玩具示例...
事实上,您可以使用状态 monad 的变体在幕后传递随机生成器。 Control.Monad.Random
中的 Rand
类型有助于此。 API 有点令人困惑,但更多的是因为它在您使用的随机生成器类型上是多态的,而不是因为它必须是函数式的。然而,这种额外的脚手架很有用,因为您可以轻松地使用不同的随机生成器重用现有代码,这使您可以测试不同的算法并明确控制生成器是确定性的(有利于测试)还是使用外部数据播种(在IO
).
这是 Rand
的一个简单示例。类型签名中的 RandomGen g =>
告诉我们可以使用 any 类型的随机生成器。我们必须明确地将 n
注释为 Int
因为否则 GHC 只知道它必须是 some 可以生成并转换为字符串的数字类型,这可以是多个可能选项之一(如 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
),并具有生成随机值的能力,让您将所有管道和设置归入你的代码。
这只是一个单子映射
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