【问题标题】:Generating a list of random values and printing them to standard output in Haskell生成随机值列表并将它们打印到 Haskell 中的标准输出
【发布时间】:2014-10-06 16:16:23
【问题描述】:

我是 Haskell 的新手,我正在努力实现一些相对简单的事情:生成随机数列表并将它们打印到标准输出。

由于随机概念与 FP 世界中的函数纯度完全相反(即,对于相同的输入,方法应该总是返回相同的结果),我理解在这种情况下,Haskell 中的 System.Random 模块返回 IO 操作.

到目前为止,我的代码如下所示:

import System.Random

randomNumber :: (Random a) => (a, a) -> IO a
randomNumber (a,b) = randomRIO(a,b)

main :: IO ()
main = do
   points <- sequence (map (\n -> randomNumber ((-1.0), 1.0)) [1..10])
   print points

这个想法很简单:生成一个包含十个随机元素的列表(可能有更好的方法来实现)。我的第一种方法是创建一个返回随机数的函数(在本例中为randomNumber,类型为IO a),并在映射元素列表时使用它(生成IO 操作列表,IO [a])。

据我了解,sequence (map (\n -&gt; randomNumber ((-1.0), 1.0)) [1..10]) 类型是IO [a],但我不知道如何使用它。我怎样才能真正使用点作为[a] 类型的值而不是IO [a]

编辑:在 do“块”中添加打印功能会产生一些我真的不知道如何摆脱的错误。

Main.hs:8:40:
    No instance for (Random a0) arising from a use of ‘randomNumber’
    The type variable ‘a0’ is ambiguous
    Note: there are several potential instances:
      instance Random Bool -- Defined in ‘System.Random’
      instance Random Foreign.C.Types.CChar -- Defined in ‘System.Random’
      instance Random Foreign.C.Types.CDouble
        -- Defined in ‘System.Random’
      ...plus 33 others
    In the expression: randomNumber ((- 1.0), 1.0)
    In the first argument of ‘map’, namely
      ‘(\ n -> randomNumber ((- 1.0), 1.0))’
    In the first argument of ‘sequence’, namely
      ‘(map (\ n -> randomNumber ((- 1.0), 1.0)) [1 .. 10])’

Main.hs:8:55:
    No instance for (Num a0) arising from a use of syntactic negation
    The type variable ‘a0’ is ambiguous
    Note: there are several potential instances:
      instance Num Double -- Defined in ‘GHC.Float’
      instance Num Float -- Defined in ‘GHC.Float’
      instance Integral a => Num (GHC.Real.Ratio a)
        -- Defined in ‘GHC.Real’
      ...plus 37 others
    In the expression: (- 1.0)
    In the first argument of ‘randomNumber’, namely ‘((- 1.0), 1.0)’
    In the expression: randomNumber ((- 1.0), 1.0)

Main.hs:8:56:
    No instance for (Fractional a0) arising from the literal ‘1.0’
    The type variable ‘a0’ is ambiguous
    Note: there are several potential instances:
      instance Fractional Double -- Defined in ‘GHC.Float’
      instance Fractional Float -- Defined in ‘GHC.Float’
      instance Integral a => Fractional (GHC.Real.Ratio a)
        -- Defined in ‘GHC.Real’
      ...plus three others
    In the expression: 1.0
    In the expression: (- 1.0)
    In the first argument of ‘randomNumber’, namely ‘((- 1.0), 1.0)’

Main.hs:9:9:
    No instance for (Show a0) arising from a use of ‘print’
    The type variable ‘a0’ is ambiguous
    Relevant bindings include points :: [a0] (bound at Main.hs:8:9)
    Note: there are several potential instances:
      instance Show Double -- Defined in ‘GHC.Float’
      instance Show Float -- Defined in ‘GHC.Float’
      instance (Integral a, Show a) => Show (GHC.Real.Ratio a)
        -- Defined in ‘GHC.Real’
      ...plus 65 others
    In a stmt of a 'do' block: print points
    In the expression:
      do { points <- sequence
                       (map (\ n -> randomNumber ((- 1.0), 1.0)) [1 .. 10]);
           print points }
    In an equation for ‘main’:
        main
          = do { points <- sequence
                             (map (\ n -> randomNumber ((- 1.0), 1.0)) [1 .. 10]);
                 print points }
Failed, modules loaded: none.

【问题讨论】:

  • 完全按照您所做的,即将结果与&lt;- 绑定在do 符号IO monad 中。你为什么不试试print points
  • 名称points 在此上下文中绑定到[a] 类型的值,因为sequence ... [1..10] 具有IO [a] 类型。 &lt;- 或“绑定”语法可以被认为是从一元上下文中“提取”一个值。在这里,你的上下文是IO,你的值的类型是[a]
  • 另外,它可能会让事情看起来更好一些:points &lt;- replicateM 10 (randomRIO (-1,1))。 (replicateM 来自Control.Monad)。
  • points 已经在[a] 类型的值范围内,用于该do 块中的任何内容。尝试在最后一行之后立即添加print points 语句,缩进与points &lt;- ...
  • @jarandaf 写成points &lt;- replicateM 10 (randomRIO (-1, 1 :: Double)) 显式类型签名应该会有所帮助。

标签: haskell


【解决方案1】:

为什么会这样?

您的所有错误中都有一条特定的消息:The type variable ‘a0’ is ambiguous。为什么会这样?好吧,randomNumber 适用于Random 的任何实例,并且有很多实例。 -1.0 包括 Num,因为您希望能够否定一个值。此外,值1.0 本身得出结论,您的类型需要是Fractional 的实例。这减少了可以在这种情况下使用的类型的数量,但它仍然不是唯一的:FloatDouble 和其他四个是合适的。

此时,编译器放弃了,需要告诉它你真正想要使用的实例。

如何解决这个问题

有很多方法可以解决这个问题。首先,我们可以引入一个小辅助函数:

-- fix a to double
randomDouble :: (Double, Double) -> IO Double
randomDouble = randomNumber

或者我们可以注释模棱两可的1.0的类型:

points <- sequence (map (\n -> randomNumber ((-1.0), 1.0 :: Double)) [1..10])
--                                                   ^^^ as a Double

或者我们可以注释列表的类型:

print (points :: [Double])
--     ^^^^^^^^^^^^^^^^^^  points is a list of Doubles

你选择哪一个实际上或多或少是风格和个人喜好的问题。话虽这么说,sequence . map f $ xs 可以写成mapM f xs,但既然你实际上有IO a,你最好用replicateM $ randomNumber (...)mapMreplicateM 都可以在 Control.Monad 中找到。

TL;DR

当 GHC 因模棱两可的类型对您大喊大叫时,请对其进行注释。

【讨论】:

  • 感谢@Zeta 的解释。我仍然需要习惯 GHC 编译器错误 :)
【解决方案2】:

几点:

  1. 您调用了函数 randomNumber,但允许它采用属于 Random 类的任何类型(包括 Chars 等)。如果您只希望它接受数字,则应更改签名以匹配其用途(randomNumber :: (Int,Int) -&gt; IO Int) 或更一般的 randomNumber :: (Num n. Random n) =&gt; (n,n) -&gt; IO n

  2. sequence 获取一个操作列表 ([IO a]),并在 IO monad (IO [a]) 中返回一个列表。它基本上只是执行每个动作,存储结果,然后在 IO 中重新包装列表。你可以试试replicateM 10 $ randomNumber (1,10)replicateM 接受一个 Int 和一个要执行的动作,并返回一个已执行动作的列表(正如 Zeta 指出的,sequence 在内部用于对 replicateM 的调用)。

(由于某种原因,代码块对我不起作用,所以我将所有内容都写为“中缀代码”。)

【讨论】:

  • 错误,replicateM :: Monad m =&gt; Int -&gt; m a -&gt; m [a],不是Int -&gt; m a -&gt; [m a](实际上是replicate)。你可能混淆了replicatereplicateM = sequence . replicate。要让代码块在列表中工作:将它们缩进 6 个(或更多)空格而不是 4 个。
  • 哎呀,你是对的。它返回排序的结果。我会修复的。谢谢。
  • 感谢@Carcigenicate 的回答。我会接受 Zeta 的,因为它看起来更完整。无论如何,再次感谢您的帮助:)
  • Np。他的答案更好。只是想如果他们有帮助的话,如果把一些东西扔出去。
猜你喜欢
  • 2013-07-25
  • 1970-01-01
  • 1970-01-01
  • 2020-08-29
  • 2013-05-01
  • 1970-01-01
  • 2014-09-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多