【问题标题】:How can a random number be used as a Double in a Haskell function?如何将随机数用作 Haskell 函数中的 Double?
【发布时间】:2018-07-28 05:49:04
【问题描述】:

我正在尝试编写一个函数,该函数使用随机数作为条件来与列表进行比较(通过将函数映射到整数范围来创建)。我以交互方式进行操作,如果我通过单独定义每个术语来进行操作,则可以:

import System.Random.MWC (create)
import Statistics.Distribution (genContVar)
import Statistics.Distribution.Uniform (uniformDistr)

rng <- create
rd <- (genContVar (uniformDistr 0 1)) rng

f x = takeWhile (<rd) $ fmap (*x) [1..10]

或者,我可以使用带有 let 表达式的非随机 Double 并且也没有问题

f x = let rd = 0.4 in takeWhile (<rd) $ fmap (*x) [1..10]

但是,如果我尝试将所有内容放在一起,则会出现错误

f x = let rand <- (genContVar (uniformDistr 0 1) g) in takeWhile (<rand) $ fmap (*x) [1..10]

<interactive>:39:16: error:
parse error on input ‘<-’
Perhaps this statement should be within a 'do' block?

我知道拥有不同的变量类型会阻止像加法和 Int 和 Double 一样多,而且 monad 非常特殊,但是作为 Haskell 的新手,我希望暂时避免更广泛的哲学,而是尝试找到一种在通用函数中使用随机数的实用方法。

【问题讨论】:

  • 拥有伪随机值的“纯”方式是向每个函数添加一个新参数(当前随机种子)和一个新输出值(修改后的种子,供以下人员使用职能)。编写这些修改过的函数可能会很烦人,StateState 之类的 monad 可用于减少涉及的样板文件并避免混淆函数的主要逻辑。唉,看看getContVar 的签名,它似乎与IOST 密不可分(因为PrimMonad 约束)所以我们不能在这里使用该技术。
  • 谢谢,@danidiaz。如果使用“纯”方法有效,我可能会尝试去做。至于其他方法,我并没有试图避免单子,但我在将原则上应该兼容的变量放在一起时遇到了麻烦。我不知道什么是“Haskell”,也没有在任何地方“学习”它,实际上我现在正试图从基本的数学和编程结构中学习一些,这些结构很容易在 Python、Julia 甚至 C++ 中实现功能。因此,我感谢您在整理一个工作示例方面的任何帮助,但请不要为您所有的“粉丝”而沾沾自喜。
  • @TwistedMersenne 也许你可以看看“随机”库hackage.haskell.org/package/random-1.1/docs/System-Random.html 你可以使用mkStdGen 创建一个种子(我们使用getStdGen,但这是一个IO 操作) . StdGen 类型是 RandomGen 的一个实例。 RandomGen 是一个类型类,有点像其他语言中的“接口”。然后您可以使用randomrandomR 之类的函数,它们获取种子并返回随机值以及修改后的种子。可以生成的类型是 Random 类型类的实例。
  • @danidiaz,谢谢。使用 randomR 可能最终会“更容易”,因为我试图将随机数与纯数学函数结合起来。这是更多的工作,我不清楚在这种情况下如何更新 rng 状态,因为 Haskell(数学上正确但令人讨厌)不允许像 (rd,rng) = randomR (0, 1) rng 这样的东西,但也许使用不同变量的额外步骤允许我更新“全球”rng 状态每次。如果您有一个可行的示例,那就太好了,但是如果可行,我会尝试发布它。

标签: haskell random types monads


【解决方案1】:

当您在 GHCi 中评估表达式时,您已经在 IO monad 中。这就是 OP GCHi 代码起作用的原因。

正如 n.m. 在评论中指出的那样,monad 在 Haskell 中使用随机数(以及所有其他非确定性或有效行为)的实用方法。如果你想编写 Haskell 代码,迟早你必须了解它们是什么。不过,好消息是,了解什么是 monad 并不像某些人认为的那么难。

Haskell 确实有 monad 的语法糖,以 do 表示法的形式。使用它,您可以编写如下函数:

f x = do
  rng <- create
  rd <- (genContVar (uniformDistr 0 1)) rng

  return $ takeWhile (<rd) $ fmap (*x) [1..10]

此函数的类型为PrimMonad m =&gt; Double -&gt; m [Double]。输出类型中PrimMonad 的存在意味着某种效果。该函数可能(并且很可能)不纯。

理论上,可以使用上述不纯语言结构编写完整的 Haskell 程序,但 Haskell 的重点是尽可能多地保持代码纯,因此应尽可能限制不纯代码的使用可能。

【讨论】:

  • 谢谢,这真的很有帮助。我应该记住 GCHi 在IO 中,所以当我编写单行函数时,我猜我不再在IO 领域。你能在一行中写一个do 语法,我可以交互使用吗?而且由于它在列表上返回 fmap 的值,它不应该只是 [Double] 吗?我猜,monad 的基础很清楚,就像 Functors 和 Applicative 一样,(&gt;&gt;=) :: (Monad m) =&gt; m a -&gt; (a -&gt; m b) -&gt; m b 的定义几乎说明了所有需要的东西。在实践中,由于 Haskell 的所有类型限制,特定情况可能不太直接。
  • @TwistedMersenne:要将do 表示法写成单行,您可以使用分号和(可选)花括号而不是依赖于布局规则:do a &lt;- b; c &lt;- d; edo { a &lt;- b; c &lt;- d; e }。这也适用于其他布局敏感的符号,例如 case exp of { pat -&gt; exp; pat -&gt; exp; … }let { pat = exp; pat = exp } in exp,因为布局是显式分隔符的语法糖。或者,您可以使用 :{:} 命令在 GHCi 中输入多行输入,或使用 :set +m 默认启用多行输入模式 - 尽管 imo 这些都有点笨重。
  • 嗯,这行得通,在 ghci 上它可以与多行环境一起使用,但无法使其与大括号一起使用。尽管如此,它还是会再次返回一个 monad 并将问题传递给下一个函数。我希望使用一元函数来最终获得一个双精度,而不必将整个程序包装在一个一元结构中,但我想这就是在 Haskell 中。也许使用纯 prng 会更好。
  • @TwistedMersenne “我希望 [...] 最终得到一个 double 而不必将整个程序包装在一个单子结构中” 也许这会有所帮助: stackoverflow.com/a/51620647/126014
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-11-16
  • 2017-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多