【问题标题】:Why can't you map print to a list in Haskell?为什么不能将打印映射到 Haskell 中的列表?
【发布时间】:2015-02-28 10:52:32
【问题描述】:

我在发这个之前检查了this 的帖子,但是那个帖子并没有真正解释为什么这不起作用。

foo :: (Num a, Show a) => a -> IO ()
foo 0 = print "Was zero"
foo x = print x

foo 2 -- prints 2
foo 5 -- prints 5

map foo [0..10] :: [IO ()] -- Doesn't print out numbers.

编辑:

bar 0 = foo 0
bar n = do
            foo n
            bar (n - 1)

确实理解为什么这会返回[IO ()],但我不明白为什么在构建此列表时不会发生打印。至少,我希望我们会看到 first 由于延迟评估而发生 print 调用。

在创建此列表的过程中,为什么没有出现打印到屏幕的“副作用”? foo 函数在应用于列表的每个元素时是否实际未输入?如果评估 print 调用以获取 IO () 来构建列表,为什么不会发生副作用?

【问题讨论】:

  • 你的foo函数不接受这个函数头的任何参数。
  • 您在编辑中的bar 函数不会返回[IO ()],而是返回IO ()。它打印所有输入(在 GHCI 中运行时),但它的返回类型绝对不是任何内容的列表,正如您通过查看它所看到的那样:任何地方都没有列表!

标签: haskell side-effects


【解决方案1】:

IO monad 中,需要IO a 之类的内容。您有[IO ()],这是一个 IO 操作列表。您可以改用mapM 来制作一元映射。 mapM f 等价于sequence . map f

【讨论】:

    【解决方案2】:

    如果评估 print 调用以使 IO () 构建列表,为什么不会发生副作用?

    因为评估IO () 类型的表达式没有任何副作用。事实上,对任何类型的表达式求值都没有任何副作用。

    【讨论】:

    • 那为什么foo 1打印1并返回IO ()
    • 评估foo 1 不会打印1。评估foo 1 `seq` return ():它将评估foo 1,但不会打印1
    【解决方案3】:

    map foo [0..10] 肯定会构造一个IO () 值的列表,但是构造一个IO () 值不会执行IO。您可能直观地知道这一点:如果我们有一个全局 IO () 并且没有引用它,它就不会被执行:

    sayHello :: IO ()
    sayHello = putStrLn "hello"
    

    您可能会将其归因于懒惰;毕竟,如果 sayHello 没有被引用,那么就没有任何东西会强制它进行评估。但是,这也无济于事:

    main = sayHello `seq` return ()
    

    在这里,我们肯定评估sayHello,但我们只评估IO ()

    IO 做某事的原因在于,当您将其组合成另一个IO(如main)并且 运行时,只有这样 才会运行它做某事:

    main = sayHello
    

    我应该注意到 GHCi 有点模糊了这种情况。 GHCi 与IO 有一些特殊的交互:如果您在提示符处键入的整个表达式产生一个IO,它将执行它并显示其结果。否则,它只显示结果。但这只是一个交互功能;在真正的 Haskell 代码中,你不能只拥有一个 IO () 并期望它神奇地变成 ()

    【讨论】:

    • 感谢您的回答-我已经开始学习 Haskell,但我还没有真正了解 monad 或 IO 的内部结构。 print 返回IO (),我一直将其读作print returns an action that returns ()。那正确吗?所以你的意思是,不是对print 的调用将1 打印到屏幕上——而是当我们评估打印返回的内容时?
    • @user2666425:有点意思,但你必须小心你的措辞。评估是纯粹的,永远不会任何事情。另一方面,执行(不仅仅是评估)print 返回的一元操作确实会将1 打印到屏幕上。
    • 您提到如果 GHCi 将执行 IO 如果表达式返回一个 - 那么为什么 GHCi 不执行每个 print 时连续执行 map print [1..10]print 1print 2 等是产生 IO 的表达式,对吧?
    • @user2666425:这些确实是产生IO 的表达式,但我在描述 GHCi 的自动执行时有点草率。 GHCi 不会在表达式中自动执行 all IO。它会尝试执行的唯一IO 是包装您在提示符下输入的整个表达式的结果,如果有IO 的话。它不会修改子表达式的评估行为。
    • 最后一个问题-我用bar 更新了我的帖子,它调用foo,然后调用它自己。为什么bar 会打印出这些值呢?我以为do会返回其中最后一个表达式的值,那么如果不是顶级表达式,GHCi为什么要执行从foo返回的每个IO ()
    【解决方案4】:

    运行IO 操作的唯一方法是将其分配给main。评估 IO 操作不会运行它们。

    如果你想在值列表上调用print,你必须做两件事:

    首先:使用mapM_ 构建一个IO 动作,prints 每个值:

    mapM_ print [1..3] :: IO ()
    

    其次,将该表达式分配给main

    main = mapM_ print [1..3]
    

    如果您遗漏了第二步(将其分配给 main 以外的其他内容),则不会发生任何事情。

    【讨论】:

    • Gracias Gabriel,mapM_ 帮了我很多忙。
    猜你喜欢
    • 2020-12-12
    • 1970-01-01
    • 2012-09-27
    • 2013-03-11
    • 1970-01-01
    • 2016-07-31
    • 2019-10-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多