В чем разница между функциями liftM и mapM?
В чем разница между лифтом и mapM в Haskell
Ответ 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 применяет функцию в монадической настройке.