Haskell单子:IO [Double] 转为 [IO Double]

9
考虑以下代码,它应该打印出随机数:
import System.Random.Mersenne

main =
 do g <- (newMTGen Nothing)
    xs <- (randoms g) :: IO [Double]
    mapM_ print xs  

运行时,我得到了一个分段错误。这并不奇怪,因为函数 'randoms' 生成了一个无限列表。假设我想要打印出 xs 的前十个值,我应该怎么做?xs 的类型是 IO [Double],我认为我需要一个类型为 [IO Double] 的变量。有哪些运算符可以在这两者之间进行转换。


顺便提一下,IO [Double] -> [IO Double] 本质上是 'sequence' 的反向类型签名。 - Gautam
1
好像是某种编译错误或者硬件问题,那么您可能需要运行 memtest86+ 进行检查。 - ehird
1
你可以执行 IO [Double] -> IO [IO Double] ... 除非你使用 unsafePerformIO - Thomas Eding
2
那个孤立的 "-}" 是相当不寻常的。 - Dan Burton
丹尼尔,这段代码对你来说正常工作吗?}是一个笔误。 - Gautam
2个回答

11
如果你遇到分段错误,并且没有使用FFI或者任何带有unsafe名称的函数,那在任何情况下都不会令人惊讶!这意味着 GHC 出现了一个 bug,或者你正在使用的库正在执行一些不安全的操作。

使用mapM_ print打印无限列表的Double是完全可以接受的;该列表将逐步处理,程序应以恒定的内存使用量运行。我怀疑你使用的System.Random.Mersenne模块存在问题,或者它所基于的 C 库存在问题,或者你的计算机存在问题(如故障的 RAM)。1请注意,newMTGen带有以下警告:

由于当前的 SFMT 库非常不纯,因此每个程序只允许一个生成器。尝试重新初始化将失败。


你最好使用提供的全局 MTGen

话虽如此,你不能以这种方式将IO [Double]转换为[IO Double];在执行IO操作之前无法知道结果列表的长度,这是不可能的,因为你有一个纯的结果(尽管其中包含IO操作)。对于无限列表,你可以编写:
desequence :: IO [a] -> [IO a]
desequence = desequence' 0
  where
    desequence n m = fmap (!! n) m : desequence (n+1) m
但是每次执行此列表中的操作时,IO [a] 操作都将被再次执行;它只会丢弃列表中其余的部分。 randoms 能够工作并返回无限个随机数的原因是它使用了 unsafeInterleaveIO 中的惰性 IO。(请注意,尽管名称中带有“不安全”一词,但这个函数 不能 导致段错误,因此可能还有其他原因导致问题。) 1 其他较少可能出现的情况包括C库编译错误或GHC中的错误。

3
只是为了记录,我认为提问者的电脑可能存在问题,而不是图书馆有问题;对我来说,提供的代码没有发生段错误。 - Daniel Wagner
3
+1 表示“你不能将 IO [Double] 转换为 [IO Double]……没有办法在不执行 IO 操作的情况下知道结果列表的长度”。 - Dan Burton
3
没问题,您可以在结果列表上使用 take 函数:mapM_ print (take 10 xs),即可打印出列表中的前10个元素。 - ehird
@DanielWagner,你说得对,当我重新编译mersenne-random包时,如果不使用-f use sse_2标志,它就可以正常工作而不会出现段错误。这有点令人担忧,因为-f use sse_2标志应该被设置,以便模块可以利用SIMD优化。 - Gautam
1
这并没有回答一个直接的问题(大约10个值),而且未能纠正原帖作者对类型的基本误解,反而讨论了一个问题错误表述的问题,好像它在这里有价值一样。(-1) - Will Ness
显示剩余2条评论

11

假设我想要打印出xs的前十个值,如何实现?

只需使用take:

main =
 do g <- (newMTGen Nothing)
    xs <- (randoms g) :: IO [Double]
    mapM_ print $ take 10 xs  

你写的代码是:

xs 的类型是 IO [Double]

但事实上,randoms g 的类型是 IO [Double],但由于使用了 do 符号,xs 的类型变成了 [Double],你可以直接对其应用 take 10

你也可以使用 liftM 跳过绑定:

main =
  do g <- newMTGen Nothing
     ys <- liftM (take 10) $ randoms g :: IO [Double]
     mapM_ print ys

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接