【问题标题】:How can I print every item in an infinite list with proper IO monad如何使用适当的 IO monad 打印无限列表中的每个项目
【发布时间】:2023-03-18 15:32:01
【问题描述】:

我有一个简单的整数定义,用一个无限列表完成:

nats = 0: map (+1) nats

我想制作一个打印每个元素的无限列表的版本。

我用Data.Trace (trace)做到了

nats = 0: map (\x -> 1 + trace (show x) x) nats

但这有点作弊。 使用 IO monad 正确执行此操作会更好。

我尝试使用以下代码使用IO [Int] 类型的无限列表:

nats' = (fmap f nats') >>= mapM (\x -> do{print x; return x})
  where f l = 0:map (+1) l

类型签名有效,但是当我尝试使用 take 10 'nats 评估它时, 它陷入无限递归而没有打印任何内容。

这段代码有什么问题,类型签名正确吗?解决这个问题的正确方法是什么?

编辑 :

我有这个问题的变体,我想创建一个包含用户所有输入的无限列表。 我该怎么做?

【问题讨论】:

  • 您为什么要尝试将生成列表的任务与打印列表的任务结合起来?只要把它们分开,你的痛苦就会少很多。

标签: haskell printing io lazy-evaluation


【解决方案1】:

IO monad 是严格的。当我们运行x <- ioAction 时,我们完全执行ioAction 并在x 绑定到一个值之前观察它的所有副作用。因此,如果ioAction 是一个无限循环,它永远不会返回要绑定到x 的值。

在您的情况下,代码具有以下形式

nats' = (fmap f nats') >>=  ...

相当于

nats' = nats' >>= return . f >>= ...

因此,nats' 将在执行 return . f 之前递归,从而导致无限循环并阻止任何输出。

如何使它真正起作用?

Haskell 最惯用的方法是将副作用与实际计算分开:我们定义一个纯值

nats = 0 : map (+1) nats

然后我们打印出来

nats' = for_ nats print

请注意,上面使用for/traverse/mapM(而不是它们的_ 变体)几乎没有意义,因为列表是无限的,因此它永远不会返回值,而只会无限执行许多印刷品。

如果你真的想交错 IO 和计算,即使这通常不那么惯用,你也可以使用递归:

nats' = go 0
  where go l = print l >>= go (l+1)

如果你想尝试返回一个列表,即使这实际上永远不会发生,你可以使用这个:

nats' = go 0
  where go l = do 
           print l
           ls <- go (l+1)
           return (l:ls)

但是,这不必要地复杂。

【讨论】:

  • 也许讨论一下您有时可能希望在某些情况下将计算与效果交错以避免泄漏的原因。
猜你喜欢
  • 1970-01-01
  • 2018-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多