我对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动作列表,{ {1}})。
根据我的理解,IO [a]
类型为sequence (map (\n -> randomNumber ((-1.0), 1.0)) [1..10])
,但我不知道如何使用它。如何才能真正将点数用作IO [a]
类型的值而不是[a]
?
编辑:在do“block”中添加print函数会产生一些我真不知道如何摆脱的错误。
IO [a]
答案 0 :(得分:4)
您的所有错误中都有一条特定消息:The type variable ‘a0’ is ambiguous
。为什么会这样?好吧,randomNumber
适用于Random
的任何实例,并且有很多实例。 -1.0
包含Num
,因为您希望能够否定某个值。此外,值1.0
本身也断定您的类型必须是Fractional
的实例。这减少了在这种情况下可以使用的类型数量,但它仍然不是唯一的:Float
,Double
和其他四种适合。
此时,编译器放弃了,你需要告诉它你真正想要使用的实例。
有很多方法可以解决这个问题。首先,我们可以引入一个小辅助函数:
-- 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 (...)
更适合您。 mapM
和replicateM
都可以在Control.Monad
中找到。
当GHC对你的模糊类型大吼大叫时,请注明它们。
答案 1 :(得分:3)
几点:
您调用函数randomNumber,但允许它采用属于Random类的任何类型(包括Chars等)。如果您只想让它取数字,您应该更改签名以符合其目的(randomNumber :: (Int,Int) -> IO Int)
或更一般,randomNumber :: (Num n. Random n) => (n,n) -> IO n
sequence
获取操作列表([IO a]
),并返回IO monad(IO [a]
)中的列表。它基本上只执行每个操作,存储结果,然后在IO中重新包装列表。你可以试试replicateM 10 $ randomNumber (1,10)
之类的东西。 replicateM
接受一个Int和一个动作来执行,并返回一个已执行动作的列表(正如Zeta指出的那样,sequence
在内部用于调用replicateM
)。
(由于某些原因,代码块对我不起作用,所以我把所有内容写成“中缀代码”。)