В чем разница между лифтом и mapM в Haskell
В чем разница между функциями liftM и mapM?
Ответы
Ответ 1
Во-первых, типы отличаются:
liftM :: (Monad m) => (a -> b) -> m a -> m b
mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]
liftM
поднимает функцию типа a -> b
к монадическому аналогу.
mapM
применяет функцию, которая дает монадическое значение списку значений, что дает список результатов, встроенных в монаду.
Примеры:
> liftM (map toUpper) getLine
Hallo
"HALLO"
> :t mapM return "monad"
mapM return "monad" :: (Monad m) => m [Char]
... обратите внимание, что map
и mapM
отличаются! Например.
> map (x -> [x+1]) [1,2,3]
[[2],[3],[4]]
> mapM (x -> [x+1]) [1,2,3]
[[2,3,4]]
Ответ 2
Они на самом деле не связаны. Я попытаюсь объяснить, что делает каждый из них. Я предполагаю, что у вас есть общее понимание того, что такое монада.
liftM :: Monad m => (a -> b) -> (m a -> m b)
позволяет использовать обычную функцию в монаде. Он принимает функцию a -> b
и превращает ее в функцию m a -> m b
, которая выполняет то же самое, что и исходная функция, но делает это в монаде. Полученная функция не "ничего" делает с монадой (она не может, потому что оригинальная функция не знала, что она была в монаде). Например:
main :: IO ()
main = do
output <- liftM ("Hello, " ++) getLine
putStrLn output
Функция ("Hello, " ++) :: String -> String
добавляет строку "Hello" к строке. Передача его в liftM
создает функцию типа IO String -> IO String
- теперь у вас есть функция, которая работает в монаде IO. Он не выполняет никаких операций ввода-вывода, но может принимать IO-действие в качестве входных данных и выдает IO-действие в качестве вывода. Поэтому я могу передать getLine
в качестве ввода, и он вызовет getLine
, добавит "Hello" к фронту результата и вернет его как действие ввода-вывода.
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
совсем другое; обратите внимание, что в отличие от liftM
он принимает монадическую функцию. Например, в монаде IO он имеет тип (a -> IO b) -> [a] -> IO [b]
. Это очень похоже на обычную функцию map
, только она применяет монадическое действие к списку и создает список результатов, завернутый в монадическое действие. Например (довольно плохой):
main2 :: IO ()
main2 = do
output <- mapM (putStrLn . show) [1, 2, 3]
putStrLn (show output)
Отпечатки:
1
2
3
[(),(),()]
То, что он делает, - это перебирать список, применяя (putStrLn . show)
к каждому элементу в списке (имея IO-эффект для печати каждого из чисел), а также преобразовывая числа в значение ()
. Полученный список состоит из [(), (), ()]
- вывода putStrLn
.
Ответ 3
Другие ответы уже хорошо объяснили это, поэтому я просто укажу, что вы обычно видите fmap
вместо liftM
в реальном коде Haskell, так как fmap
- это просто более общая версия в типе class Functor
. Поскольку все корректные Monad
должны быть экземплярами Functor
, они должны быть эквивалентными.
Вы также можете видеть, что оператор <$>
используется как синоним для fmap
.
Кроме того, mapM f = sequence . map f
, поэтому вы можете думать об этом как об изменении списка значений в списке действий, а затем выполнять действия один за другим, собирая результаты в списке.
Ответ 4
liftM
и mapM
совершенно разные, как вы можете видеть через их типы и их реализацию:
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM f as = sequence (map f as)
liftM :: (Monad m) => (a1 -> r) -> m a1 -> m r
liftM f m1 = do { x1 <- m1; return (f x1) }
поэтому, пока mapM
применяет монадическую функцию к каждому элементу списка, liftM
применяет функцию в монадической настройке.