haskell中forM和forM_有什么区别?

Xav*_*hay 14 haskell

HLint建议我使用forM_而不是forM.为什么?我看到他们有不同类型的签名,但没有找到一个很好的理由使用一个而不是另一个.

forM  :: (Traversable t, Monad m) => t a -> (a -> m b) -> m (t b)
forM_ :: (Foldable t,    Monad m) => t a -> (a -> m b) -> m ()
Run Code Online (Sandbox Code Playgroud)

Die*_*Epp 25

forM_功能更有效,因为它不会保存操作的结果.就这些.(这在使用monad时才有意义,因为类型的纯函数a -> ()不是特别有用.)

  • @Federico这不完全正确.即使未计算列表的元素,仍然存在创建列表主干的开销.在`forM`调用期间还存在空间泄漏,因为它保留了列表元素. (11认同)
  • 另外,使用`forM_`在语义上更好*; 它告诉读者,效果是这种一元行动的唯一重要部分. (3认同)

0xA*_*xAX 11

好,

forM is mapM with its arguments flipped.

forM_ is mapM_ with its arguments flipped.
Run Code Online (Sandbox Code Playgroud)

我们在mapM和mapM_中看到:

mapM :: Monad m => (a -> m b) -> [a] -> m [b]
Run Code Online (Sandbox Code Playgroud)

mapM mf xs采用monadic函数mf(具有类型Monad m => (a -> m b))并将其应用于列表中的每个元素xs; 结果是monad中的列表.mapM和之间的区别mapM_是,mapM返回结果列表,同时mapM_返回空结果.mapM_不存储每个操作的结果.


Jam*_*son 7

要理解(A) :forM xs f(B) :之间的区别forM_ xs f,比较以下各项之间的区别可能会有所帮助:

\n\n
-- Equivalent to (A)\ndo\n  r1 <- f x1\n  r2 <- f x2\n  ...\n  rn <- f xn\n  return [r1, r2, ..., rn]\n\n-- Equivalent to (B)\ndo\n  _ <- f x1\n  _ <- f x2\n  ...\n  _ <- f xn\n  return ()\n
Run Code Online (Sandbox Code Playgroud)\n\n

关键的区别在于forM_忽略结果r1,...rn并且仅通过返回空结果return ()。将下划线视为“不关心”的意思...forM_ 不关心结果。forM但是,确实关心结果并通过return [r1, r2, ... rn].

\n\n
\n\n

示例 1 \n下面的代码询问您的姓名 3 次并打印 forM 的结果。

\n\n
import Control.Monad (forM, forM_)\n\nmain = do\n  let askName i = do\n      putStrLn $ "What\'s your name (" ++ (show i) ++ ")"\n      name <- getLine\n      return name\n  results <- forM [1,2,3] askName\n  putStrLn $ "Results = " ++ show results\n
Run Code Online (Sandbox Code Playgroud)\n\n

执行示例forM

\n\n
What\'s your name? (1)\n> James\nWhat\'s your name? (2)\n> Susan\nWhat\'s your name? (3)\n> Alex\nResults = ["James", "Susan", "Alex"]\n
Run Code Online (Sandbox Code Playgroud)\n\n

但如果我们将 更改forM为 a forM_,那么我们将改为:

\n\n
What\'s your name? (1)\n> James\nWhat\'s your name? (2)\n> Susan\nWhat\'s your name? (3)\n> Alex\nResults = ()\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

在您的情况下,linter 告诉您您没有使用您的返回值forM(您没有foo <- forM xs f,您可能forM xs f在一行上有它自己),因此应该使用forM_。例如,当您使用诸如 之类的单子操作时,就会发生这种情况putStrLn

\n\n
\n\n

示例 2下面的代码询问您的姓名,然后重复 3 次说“Hello”\xe2\x80\x93。

\n\n
import Control.Monad (forM, forM_)\n\nmain = do\n  let askThenGreet i = do\n      putStrLn $ "What\'s your name (" ++ (show i) ++ ")"\n      name <- getLine\n      putStrLn $ "Hello! " ++ name\n  forM [1,2,3] askThenGreet\n
Run Code Online (Sandbox Code Playgroud)\n\n

执行示例forM

\n\n
 What\'s your name? (1)\n> Sarah\nHello! Sarah\n\nWhat\'s your name? (2)\n> Dick\nHello! Dick\n\nWhat\'s your name? (3)\n> Peter\nHello! Peter\n\n[(), (), ()]\n
Run Code Online (Sandbox Code Playgroud)\n\n

的总体结果main来自于 forM: 的结果[(), (), ()]。它出现在控制台中,非常无用且令人烦恼。但如果我们将 更改forM为 a forM_,那么我们将改为:

\n\n
What\'s your name? (1)\n> Sarah\nHello! Sarah\n\nWhat\'s your name? (2)\n> Dick\nHello! Dick\n\nWhat\'s your name? (3)\n> Peter\nHello! Peter\n
Run Code Online (Sandbox Code Playgroud)\n\n

通过这一更改,总体结果来自 和mapM_现在()。这不会显示在控制台中(IO monad 的一个怪癖)!伟大的!\n此外,通过mapM_在这里使用,代码的其他读者会更清楚 \xe2\x80\x93 你间接解释/自我记录你不关心结果[r1, ..., rn] = [(), (), ()]\xe2\x80\x93这是正确的,因为它们在这里毫无用处。

\n