在Haskell中循环Monad

8

我是Haskell的新手,所以这可能是一个愚蠢的问题。我有一个函数

foo :: Int -> IO ()

其结果将打印一些有用的信息。现在我想要做到这一点:

do
    foo 0
    foo 1
    foo 0
    foo 2
    foo 3

我该如何用循环来写这个程序?我的问题是“连接”单子,这一过程在do语句中自动完成...

谢谢你的帮助!


你的意思是这个序列 [0, 1, 0, 2, 0, 3] 吗? - poitroae
1
只是让你知道,你在错误地使用术语。IO是一个单子,IO ()(以及IO IntIO a等)是一种类型,而foo 0是类型为IO ()的值(类似于foo 0的常见词汇是“动作”,“单子动作”或“单子计算”)。因此,您正在尝试连接(或更常见的是“序列化”)操作,而不是单子-这里只有一个单子,它是IO - luqui
2个回答

14

mapM_ foo [0,1,0,2,3] 可以完成任务。

更重要的是,“如何找到这个方法?” Hoogle 是一个很好的工具。你想将一个带有签名 Int -> IO () 的函数应用于一堆 Int,以得到一个新的 IO 操作。因此,你要找的东西将具有签名 (Int -> IO ()) -> [Int] -> IO (),所以我们去 询问 Hoogle 是否有该签名的函数。第二个结果是 mapM_,其签名为

Monad m => (a -> m b) -> [a] -> m ()

没错,实际上mapM_可以与任何单子一起使用(不仅限于IO),并且可以处理任何类型(不仅限于Int)。仔细想想,这并不奇怪。


谢谢你提供的 Hoogle 提示!那将非常非常有帮助 :) - Sh4pe

12

您需要使用mapM_组合子,它将一个返回单子值的函数映射到列表上,并使用绑定运算符对结果进行排序:

>> let foo n = putStrLn (show n ++ "!")
>> mapM_ foo [0,1,0,2,3]
0!
1!
0!
2!
3!
有时人们喜欢使用翻转的版本。
for :: Monad m => [a] -> (a -> m b) -> m ()
for = flip mapM_

看起来更像命令式代码:

>> for [1..5] $ \n ->
     putStrLn ("Number: " ++ show n)
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5

注意,在Control.Monad中定义了一个名为forM_的组合子,它与我所称呼的for组合子完全相同。


1
什么?那个编辑是关于什么的?forM_是正确的名称,因为标准库中使用了它。 - leftaroundabout
既然我提供了定义,我觉得我可以随便称呼它。将其称为“for”可以更清晰地与命令式语言进行类比。而且这里有先例。我认为 Stack Overflow 上的答案(主要是教学性质的)不一定要受标准库约定的限制(需要权衡教学和方便性与适应其他同名函数的需要)。 - Chris Taylor

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