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