В чем разница между 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