我只是对列表和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 即可。谢谢大家的回答。我为这种模糊的问题道歉。我不知道我需要什么答案,问题是什么。这是因为我不了解一些基础知识。因此,现在很难选择“正确的答案”,因为每个答案都有我想要的平静。我决定选择最接近的(虽然现在不是最有用的)。
答案 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
你准备好了吗?函数forM
和forM_
只是mapM
和mapM_
,其参数相反,即:
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
只会在空列表中崩溃,方式与head
和tail
相同。