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

#haskell #functional-programming #monads #combinators

#haskell #функциональное программирование #монады #комбинаторы

Вопрос:

В чем разница между функциями liftM и mapM?

Комментарии:

1. @larsmans Этот вопрос не имеет никакого отношения к mapM.

2. @mgiuca: вы правы. Извините. Я думаю, мне следует прочитать более внимательно.

Ответ №1:

На самом деле они не связаны. Я попытаюсь объяснить, что делает каждый из них. Я предполагаю, что у вас есть базовое понимание того, что такое монада.

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 — теперь у вас есть функция, которая работает в монаде ввода-вывода. Он не выполняет никакого ввода-вывода, но может принимать действие ввода-вывода в качестве входных данных и производить действие ввода-вывода в качестве выходных данных. Следовательно, я могу передать getLine в качестве входных данных, и он вызовет getLine , добавит «Hello» в начало результата и вернет это как действие ввода-вывода.

mapM :: Monad m => (a -> m b) -> [a] -> m [b] сильно отличается; обратите внимание, что в отличие от liftM , оно использует монадическую функцию. Например, в монаде ввода-вывода она имеет тип (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) к каждому элементу в списке (с эффектом ввода-вывода, заключающимся в распечатке каждого из чисел), а также преобразование чисел в () значение. Результирующий список состоит из [(), (), ()] — выходных данных putStrLn .

Комментарии:

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

2. @Tom Lokhorst Да, это верно. (Я не смог придумать полезный пример, где результат важен для монады ввода-вывода.)

3. Почему mapM преобразует числа в ‘()’?

4. @Luke putStrLn :: String -> IO () Возвращает единичное значение (то есть () ). Вот еще один пример: mapM (x -> putStrLn (show x) >> return (x 1)) [1, 2, 3] . Это возвращает список целых чисел вместо единиц.

Ответ №2:

Во-первых, типы различаются:

 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]]
  

Комментарии:

1. Есть ли это (x -> [x 1]) в лямбде для map? Я попробовал синтаксис без обратной косой черты для x, и я получаю Pattern syntax in expression context: x -> [x 1] . Я новичок в Haskell, поэтому, возможно, я что-то здесь упускаю.

Ответ №3:

В других ответах это уже хорошо объяснено, поэтому я просто укажу, что вы обычно увидите, что в реальном коде Haskell fmap используется вместо liftM , поскольку fmap это просто более общая версия в классе type Functor . Поскольку все хорошо управляемые Monad s также должны быть экземплярами 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 применяет функцию в монадической настройке.