以新行打印列表元素

时间:2012-09-17 14:52:50

标签: list haskell monads monad-transformers

我只是对列表和monad感到困惑,所以也许我的问题不正确或非常幼稚。 我已经看到了使用mapM_ func here

的方法
mapM_ print [1, 2, 3, 4]

但我不确切知道它是如何工作的,我想知道如何以这样的方式做到这一点:

x <- [1, 2, 3]
print x

或者,如果我理解正确的话:

[1, 2, 3] >>= print

我了解[1,2,3]的类型为[a],而print的类型为Show a => a -> IO ()。另外我理解,对于使用monad List,我们需要在左侧键入List a,在右侧键入a -> List b类型的func。我对吗? 你能帮我解决这个问题吗?

UPD 即可。感谢@MathematicalOrchid解释mapM_如何工作。从我的角度来说,我想解释一下,真正的问题不是在不同的行中打印任何结果,而是以monad List提供的方式做一些monadic动作(因为现在我正在处理OpenGL的东西)。但我得知误解的根源在于混合单子。

UPD2 即可。谢谢大家的回答。我为这种模糊的问题道歉。我不知道我需要什么答案,问题是什么。这是因为我不了解一些基础知识。因此,现在很难选择“正确的答案”,因为每个答案都有我想要的平静。我决定选择最接近的(虽然现在不是最有用的)。

5 个答案:

答案 0 :(得分:12)

你似乎在这里混淆了几件事。 (特别是,列表形成一个monad,I / O形成一个不同的monad。)我会尝试清除它......

首先,print函数显示任何可显示的内容并将其写入标准输出,然后是换行符。所以print [1, 2, 3]工作得很好,但很明显将所有内容写在同一行。要在单独的行上写东西,我们需要为每个项目单独调用print。到目前为止,非常好。

map函数将函数应用于列表的每个元素。因此map print [1, 2, 3]会将print应用于列表中的每个项目。 但是,结果是I / O操作列表。这不是我们所追求的。我们希望执行这些操作,而不是列出它们。

执行此操作的方法是使用>>运算符,它将两个I / O操作链接在一起(假设您对其结果不感兴趣 - 并且打印某些内容不会返回任何有趣的内容)。因此foldr (>>) (return ())将获取您的I / O操作列表并将其转换为单个I / O操作。事实上这个功能已经定义了;它被称为sequence

但是,map + sequence是一种常见的组合,也已定义;它被称为mapM_。 (如果你想保留结果,还有mapM,没有下划线。但是打印不返回任何内容,所以没有必要。)


现在,这就是mapM_的原因。现在你问为什么其他几种方法不起作用......

x <- [1, 2, 3]
print x

这根本不起作用。第一行是monad列表。但第二行是在I / O monad中。你不能这样做。 (你会得到一个相当令人困惑的类型检查错误。)我应该指出这是Haskell所谓的“do-notation”,上面的片段需要前面的do关键字才能真正实现有效的语法:

do
  x <- [1, 2, 3]
  print x

无论哪种方式,它仍然无效。 几乎执行map print [1, 2, 3]所做的事情,但并不完全。 (正如我所说,它不会进行打字检查。)

您还建议使用[1, 2, 3] >>= print,这与前一个代码段完全相同。 (事实上​​,编译器将前者转换为后者。)原始版本不进行类型检查,并且由于同样的原因,这也不进行类型检查。

这有点像尝试在矩阵中添加数字。数字是可添加的东西。 Matricies是可添加的东西。但你不能将一个添加到另一个,因为它们不一样。如果这是有道理的。

答案 1 :(得分:9)

你想要的东西不能以这种方式工作,因为你试图将两个monad混合在一起:

do x <- [1,2,3]
   print x

具体而言,您正在混合IO[] monad。在do-notation中,对于某些Monad m a,所有语句都应具有m类型。但在上面的代码中,第一个语句的类型为[Integer],而第二个语句的类型为IO ()

要获得您想要的效果,您应该使用ListT monad变换器。 Monad transformers允许在一个堆栈中以特定顺序混合monad并根据需要组合它们的效果。

import Control.Monad.Trans
import Control.Monad.Trans.List

value = do x <- ListT (return [1,2,3])
           lift (print x)

这将返回ListT IO Integer类型的值。要从此变换器中获取IO计算,请使用runListT。这将返回IO [Integer]类型的值。这将输出:

GHCI> runListT value
1
2
3
[(),(),()]

相当于mapM print [1,2,3]。要丢弃列表并获得mapM_ print [1,2,3]的效果,您可以使用void中的Control.Monad

GHCI> void . runListT $ value
1
2
3

答案 2 :(得分:5)

您可以使用sequence_按顺序执行IO操作:

sequence_ $ [1, 2, 3] >>= (\x -> [print x])

但我认为mapM_相当清楚。

答案 3 :(得分:4)

我不会完全回答你的问题,因为我认为问题本身有点误导。特别是,在这里使用mapM或类似的东西正确的做法。使用do notation进行此任务只会使其变得更加复杂,我厌恶告诉人们那些不是正确的事情的事情。但我会为您提供一种您可能更容易消化的替代方案。

如果你来自一个必要的背景(即你熟悉C,Java,Python等等),那么你可能会发现使用forM而不是mapM更容易。语法是

forM <list of things> <action to perform for each thing>

即。它就像一个for-each循环!例如:

ghci> import Control.Monad
ghci> forM [1,2,3] print
1
2
3
[(),(),()]

事物列表为[1,2,3],每件事情的执行动作为print。注意结尾的返回值?这是因为对print的每次调用都会返回(),并且这些调用会在最后收集到一起。如果您不想要返回值,请使用forM_代替forM,如下所示:

ghci> forM_ [1,2,3] print
1
2
3

你准备好了吗?函数forMforM_只是mapMmapM_,其参数相反,即:

forM list action = mapM action list

我经常在代码中使用forM,因为它会引起对函数的注意,而不是 list ,这通常是你想要的。当函数跨越多行时,它看起来也更整洁。

答案 4 :(得分:1)

这可能是mapM_如何运作的最简单的解释:

main = foldr1 (>>) (map print [1, 2, 3])

也就是说,print应用于每个列表成员,结果使用>>加入,所以首先得到

main = foldr1 (>>) [print 1, print 2, print 3]

最后得到

main = print 1 >> print 2 >> print 3 

更精确的解释是:

main = foldr (>>) (return ()) (map print [1, 2, 3])

所以最后你得到了

main = print 1 >> print 2 >> print 3 >> return ()

return ()部分允许函数使用空列表 - foldr1只会在空列表中崩溃,方式与headtail相同。