В отличие от Functor, Монада может изменить форму?
Мне всегда нравилось следующее интуитивное объяснение власти монады относительно функтора: монада может изменять форму; функтор не может.
Например: length $ fmap f [1,2,3]
всегда равно 3
.
Вместе с монадой length $ [1,2,3] >>= g
часто не будет равняться 3
. Например, если g
определяется как:
g :: (Num a) => a -> [a]
g x = if x==2 then [] else [x]
то [1,2,3] >>= g
равно [1,3]
.
То, что меня немного беспокоит, - это подпись типа g
. Кажется невозможным определить функцию, которая изменяет форму ввода, с общим монадическим типом, таким как:
h :: (Monad m, Num a) => a -> m a
В классах классов MonadPlus или MonadZero используются соответствующие нулевые элементы, вместо []
, но теперь у нас есть нечто большее, чем монада.
Правильно ли я? Если да, то есть способ выразить эту тонкость новичку в Haskell. Я хотел бы, чтобы моя любимая фраза "Монад может изменить форму", просто прикосновение более честное; если необходимо.
Ответы
Ответ 1
Мне всегда нравилось следующее интуитивное объяснение власти монады относительно функтора: монада может изменять форму; функтор не может.
Вы, кстати, упустили немного тонкости. По терминологии я разделил бы Functor
в смысле Хаскелла на три части: Параметрический компонент, определяемый параметром типа и управляемый fmap
, неизменные части, такие как конструктор кортежа в State
, и "форма", как и все остальное, например, выбор между конструкторами (например, Nothing
vs. Just
) или части, содержащие другие параметры типа (например, среда в Reader
).
Конечно, A Functor
ограничен функциями отображения по параметрической части.
A Monad
может создавать новые "фигуры" на основе значений параметрической части, что позволяет гораздо больше, чем просто изменять формы. Дублирование каждого элемента в списке или удаление первых пяти элементов изменит форму, но для фильтрации списка необходимо проверить элементы.
Это, по существу, то, как Applicative
подходит между ними - это позволяет независимо друг от друга комбинировать формы и параметрические значения двух Functors
, не позволяя последним влиять на первое.
Правильно ли я? Если да, то есть способ выразить эту тонкость новичку в Haskell. Я хотел бы, чтобы моя любимая фраза "Монад может изменить форму", просто прикосновение более честное; если необходимо.
Возможно, тонкость, которую вы ищете, заключается в том, что вы на самом деле ничего не меняете. Ничто в Monad
не позволяет явно связываться с формой. Он позволяет создавать новые фигуры на основе каждого параметрического значения, а новые формы объединяются в новую составную форму.
Таким образом, вы всегда будете ограничены доступными способами создания фигур. С полностью общим Monad
все, что у вас есть, это return
, который по определению создает любую форму, необходимую, чтобы (>>= return)
была функцией идентичности. Определение a Monad
говорит вам, что вы можете делать, учитывая определенные виды функций; он не предоставляет эти функции для вас.
Ответ 2
Операции Monad
могут "изменять форму" значений в той степени, в которой функция >>=
заменяет листовые узлы в "дереве", которая является исходным значением, с новой субструктурой, полученной из значения node (для подходящее общее понятие "дерева" - в случае списка "дерево" ассоциативно).
В вашем примере списка происходит то, что каждый номер (лист) заменяется новым списком, который возникает, когда g
применяется к этому номеру. Общая структура исходного списка все еще может быть видна, если вы знаете, что ищете; результаты g
все еще существуют по порядку, они просто разбиты вместе, поэтому вы не можете сказать, где заканчивается, а следующее начинается, если вы уже не знаете.
Более понятной точкой зрения может быть рассмотрение fmap
и join
вместо >>=
. Вместе с return
в любом случае дает эквивалентное определение монады. Однако в представлении fmap
/join
то, что здесь происходит, более понятно. Продолжая свой список, сначала g
есть fmap
ped над списком, дающим [[1],[],[3]]
. Тогда этот список join
ed, который для списка просто concat
.
Ответ 3
Просто потому, что шаблон монады включает некоторые конкретные экземпляры, которые допускают изменения формы, не означает, что каждый экземпляр может иметь изменения формы. Например, в монаде Identity
имеется только одна "форма":
newtype Identity a = Identity a
instance Monad Identity where
return = Identity
Identity a >>= f = f a
На самом деле мне не ясно, что очень много монадов имеют значимые "формы": например, что означает форма в State
, Reader
, Writer
, ST
, STM
, или IO
monads?
Ответ 4
Комбинатор клавиш для монадов (>>=)
. Зная, что он составляет два монадических значения и считывает подпись своего типа, сила монадов становится более очевидной:
(>>=) :: Monad m => m a -> (a -> m b) -> m b
Будущее действие может полностью зависеть от результата первого действия, потому что оно является функцией его результата. Эта власть идет по цене, хотя: Функции в Haskell полностью непрозрачны, так что вы не можете получить какую-либо информацию о скомпилированном действии, фактически не запуская ее. Как побочная заметка, здесь появляются стрелки.
Ответ 5
Функция с сигнатурой, подобной h
, действительно не может делать много интересного, кроме выполнения некоторой арифметики по ее аргументу. Итак, у вас есть правильная интуиция.
Тем не менее, это может помочь посмотреть на часто используемые библиотеки для функций с похожими сигнатурами. Вы обнаружите, что наиболее общие из них, как и следовало ожидать, выполняют общие операции монады, такие как return
, liftM
или join
. Кроме того, когда вы используете liftM
или fmap
, чтобы поднять обычную функцию в монадическую функцию, вы обычно набираете аналогичную общую подпись, и это вполне удобно для интеграции чистых функций с монадическим кодом.
Чтобы использовать структуру, которую предлагает конкретная монада, вам неизбежно нужно использовать некоторые знания о конкретной монаде, в которой вы собираетесь строить новые и интересные вычисления в этой монаде. Рассмотрим государственную монаду, (s -> (a, s))
. Не зная этого типа, мы не можем писать get = \s -> (s, s)
, но не имея возможности получить доступ к состоянию, в монаде мало смысла.
Ответ 6
Простейший тип функции, удовлетворяющей требованию, которое я могу себе представить, таков:
enigma :: Monad m => m () -> m ()
Можно реализовать его одним из следующих способов:
enigma1 m = m -- not changing the shape
enigma2 _ = return () -- changing the shape
Это было очень простое изменение - enigma2
просто отбрасывает форму и заменяет ее тривиальным. Другим видом общих изменений является объединение двух форм вместе:
foo :: Monad m => m () -> m () -> m ()
foo a b = a >> b
Результат foo
может иметь форму, отличную от a
и b
.
Третье очевидное изменение формы, требующее полной мощности монады, - это
join :: Monad m => m (m a) -> m a
join x = x >>= id
Форма join x
обычно не совпадает с формой x
.
Объединяя эти примитивные изменения формы, можно получить нетривиальные вещи, такие как sequence
, foldM
и аналогичные.
Ответ 7
ли
h :: (Monad m, Num a) => a -> m a
h 0 = fail "Failed."
h a = return a
соответствует вашим потребностям? Например,
> [0,1,2,3] >>= h
[1,2,3]
Ответ 8
Это не полный ответ, но у меня есть несколько вещей, чтобы сказать о вашем вопросе, который действительно не вписывается в комментарий.
Во-первых, Monad
и Functor
являются typeclasses; они классифицируют типы. Так что странно говорить, что "монада может изменить форму, функтор не может". Я считаю, что вы пытаетесь говорить о "монадическом значении" или, возможно, "монадическом действии": значение, тип которого m a
для некоторого Monad m
вида * -> *
и другого типа типа *
, Я не совсем уверен, что назвать Functor f :: f a
, я полагаю, я назвал бы это "значением в функторе", хотя это не лучшее описание, скажем, IO String
(IO
является функтором).
Во-вторых, обратите внимание, что все монады обязательно являются Функторами (fmap = liftM
), поэтому я бы сказал, что разница, которую вы наблюдаете, находится между fmap
и >>=
, или даже между f
и g
, а не между Monad
и Functor
.