В чем разница между mapM_ и mapM в Haskell?

Я уже проверил hoogle, http://hackage.haskell.org/package/base-4.7.0.1/docs/Prelude.html#v:mapM

Hoogle говорит mapM_ игнорировать результаты.

Но я до сих пор не знаю, как правильно работать.

main = mapM_ (putStrLn.show) [1,2]

main = mapM (putStrLn.show) [1,2]

main = map (putStrLn.show) [1,2]

Ответы

Ответ 1

mapM_ полезен для выполнения чего-либо только для его побочных эффектов. Например, печать строки на стандартный вывод не возвращает ничего полезного - она ​​возвращает (). Если у нас есть список из трех строк, мы в итоге накапливаем список [(), (), ()]. Построение этого списка имеет затраты времени исполнения, как с точки зрения скорости, так и использования памяти, поэтому, используя mapM_, мы можем полностью пропустить этот шаг.

Однако иногда нам нужно выполнять побочные эффекты и составлять список результатов. Если у нас есть такая функция, как

lookupUserById :: UserId -> IO User

Затем мы можем использовать это, чтобы раздуть список UserId в список User s:

lookupUsers :: [UserId] -> IO [User]
lookupUsers = mapM lookupUserById

Ответ 2

Основная идея состоит в том, что mapM отображает "действие" (т.е. функцию типа a -> m b) над списком и дает вам все результаты как m [b]. mapM_ делает то же самое, но никогда не собирает результаты, возвращая m ().

Если вам нужны результаты вашей функции a -> m b (т.е. b s), используйте mapM. Если вы только заботитесь об эффекте, что бы это ни было, но не результирующем значении, используйте mapM_, потому что он может быть более эффективным и, что более важно, делает ваши намерения понятными.

Вы всегда использовали бы mapM_ с функциями типа a -> m (), например print или putStrLn. Эти функции возвращают (), чтобы показать, что имеет значение только эффект. Если вы использовали mapM, вы получили бы список () (т.е. [(), (), ()]), что было бы совершенно бесполезно, но потеряло бы некоторую память. Если вы используете mapM_, вы просто получите (), но он все равно распечатает все.

С другой стороны, если вы заботитесь о возвращаемых значениях, используйте mapM. В качестве гипотетического примера, представьте себе функцию fetchUrl :: Url -> IO Response -выход, вы заботитесь о ответе, который вы получаете за каждый URL. Поэтому для этого вы должны использовать mapM, чтобы получить списки ответов, которые затем можно использовать в остальной части вашего кода.

Итак: mapM если вы заботитесь о результатах и ​​ mapM_, если вы этого не сделаете.

Обычный map - это что-то другое: вместо обычного использования монады (a -> m b) требуется обычная функция (a -> b). Это означает, что он не может иметь никакого эффекта, кроме возвращения измененного списка. Вы бы использовали его, если хотите преобразовать список, используя обычную функцию. map_ не существует, потому что, поскольку у вас нет никаких эффектов, вы всегда заботитесь о результатах использования map.

Ответ 3

В более общих терминах разница заключается в том, что mapM_ требуется "потреблять" все элементы ввода, тогда как mapM также необходимо "перестроить" структуру данных с новыми значениями. Это довольно тривиально для списков: вам нужно только перемещаться вдоль cons-ячеек, обновляя значения с помощью полученных вами действий. Но это не так легко для контейнеров, структура которых зависит от фактических содержащихся значений. Так, например, для Data.Set вы не можете определить эквивалент mapM с типом (a -> m b) -> Set a -> m (Set b) – это экземпляр Foldable, но не Traversable

Ответ 4

mapM_ игнорирует результат. Это означает, что возвращаемое значение не будет возвращено. Вы можете увидеть это в интерактивном режиме:

$ ghci
GHCi, version 7.6.3: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.

Prelude> mapM (putStrLn . show ) [1,2]
1
2
[(),()]
Prelude> mapM_ (putStrLn . show ) [1,2]
1
2