Подтвердить что ты не робот

В отличие от 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. Я хотел бы, чтобы моя любимая фраза "Монад может изменить форму", просто прикосновение более честное; если необходимо.

4b9b3361

Ответ 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.