【问题标题】:Randomness in a nested pure function嵌套纯函数中的随机性
【发布时间】:2015-05-07 10:16:29
【问题描述】:

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

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

【问题讨论】:

  • 因为你的第一个想法是跳转到一个monad,你会喜欢MonadRandom
  • “那个生成器必须通过整个调用堆栈”——这是一件好事。我想知道哪些函数是不纯的。使用随机性的函数不是纯粹的; Haskell 通过显式传递一个随机数生成器来处理这个问题。

标签: haskell functional-programming monads purely-functional


【解决方案1】:

这只是一个单子映射

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

【讨论】:

    【解决方案2】:

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

    下面是Rand 的一个简单示例。类型签名中的RandomGen g =&gt; 告诉我们,我们可以为它使用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,它允许您扩展其他单子(如IO),使其能够生成随机值,让您将所有管道和设置降级为代码的一部分。

    【讨论】:

    • Haskell 中的随机性让我恼火的是,对伪随机数和真正的随机数使用相同的代码似乎几乎需要惰性 I/O,这已被广泛回避。如果您承诺使用伪随机数,然后决定切换到真正随机数,事情会变得非常棘手。也许pipesconduit 或一些箭头库提供了一个很好的解决方案;我不知道。
    • 谢谢。但是嵌套呢?现在你直接从 IO monad 调用这个函数。如果我们想从另一个函数调用这个函数,那么我们必须让那个函数的类型为 X -> Rand g Y,对吧?
    • 是的,返回类型必须是Rand。作为 monad 的优点是我们可以轻松地组合 a -&gt; Rand g b 类型的函数,而无需明确底层管道。这让我们可以使用类型来标记哪些函数依赖于随机性(我们不能直接返回 b,因为它会丢失此信息),而实际上将它们组合在一起并不会有太多麻烦。
    • @piotrek 关键是嵌套调用的其他层中涉及的所有函数依赖于(并影响)随机生成器状态;在类型中明确说明这一点是一件好事。应对这一挑战的函数式编程方法是提出使这些函数(如 Monads)的工作变得容易的抽象。应对这一挑战的命令式编程方法是使信息流隐式化。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-03
    • 2019-09-24
    • 2017-09-29
    • 2017-04-28
    相关资源
    最近更新 更多