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