Есть ли какая-то интуиция для понимания объединения двух функций в Монаде?

join определяется вместе с bind, чтобы сгладить объединенную структуру данных в единую структуру.

Из системного представления типа (+) 7 :: Num a => a -> a можно рассматривать как Functor, (+) :: Num a => a -> a -> a можно рассматривать как Functor of Functor, как получить некоторую интуицию об этом, а не просто полагаться на систему типов? Почему join (+) 7 === 14?

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

Это из упражнений NICTA.

-- | Binds a function on the reader ((->) t).
--
-- >>> ((*) =<< (+10)) 7
-- 119
instance Bind ((->) t) where
  (=<<) ::
    (a -> ((->) t b))
    -> ((->) t a)
    -> ((->) t b)
  (f =<< a) t =
    f (a t) t

-- | Flattens a combined structure to a single structure.
--
-- >>> join (+) 7
-- 14
join ::
  Bind f =>
  f (f a)
  -> f a
join f =
  id =<< f

*Course.State> :t join (+)
join (+) :: Num a => a -> a
*Course.State> :t join
join :: Bind f => f (f a) -> f a
*Course.State> :t (+)
(+) :: Num a => a -> a -> a

Ответ 1

как получить некоторую интуицию об этом, а не просто полагаться на систему типов?

Я бы сказал, что полагаться на систему типов - отличный способ создать конкретную интуицию. Тип join:

join :: Monad m => m (m a) -> m a

Специализируется на (->) r, он становится:

(r -> (r -> a)) -> (r -> a)

Теперь попробуйте определить join для функций:

-- join :: (r -> (r -> a)) -> (r -> a)
join f = -- etc.

Мы знаем, что результат должен быть функцией r -> a:

join f = \x -> -- etc.

Однако мы ничего не знаем о том, что такое типы r и a, и поэтому мы ничего не знаем в частности о f :: r -> (r -> a) и x :: r. Наше невежество означает, что мы можем сделать с ними только одну вещь: передав x в качестве аргумента, как в f, так и в f x:

join f = \x -> f x x

Следовательно, join для функций пропускает один и тот же аргумент дважды, потому что это единственно возможная реализация. Конечно, эта реализация является лишь правильной монадической join, поскольку она следует за законами монады:

join . fmap join = join . join
join . fmap return = id
join . return = id

Проверка того, что может быть другим приятным упражнением.

Ответ 2

Следуя традиционной аналогии монады как контекста для вычисления, join - это метод объединения контекстов. Начните с вашего примера. join (+) 7. Использование функции в качестве монады подразумевает монаду-читателю. (+ 1) - это монада-читатель, которая берет среду и добавляет ее к ней. Таким образом, (+) будет монадом-читателем в монаде-читателе. Монада внешнего читателя берет среду n и возвращает читателя формы (n +), которая примет новую среду. join просто объединяет две среды, поэтому вы предоставляете их один раз и дважды применяете данный параметр. join (+) === \x -> (+) x x.

Теперь, в общем, рассмотрим другие примеры. Монада Maybe представляет потенциальный сбой. Значение Nothing является неудачным вычислением, тогда как a Just x является успешным. A Maybe в пределах Maybe - это вычисление, которое может выходить из строя дважды. Значение Just (Just x), очевидно, является успешным, поэтому объединение, которое дает Just x. A Nothing или Just Nothing указывает на сбой в какой-то момент, поэтому объединение возможного сбоя должно указывать на то, что вычисление завершилось неудачно, т.е. Nothing.

Аналогичная аналогия может быть сделана для монады списка, для которой join является просто concat, монада-писатель, которая использует моноидальный оператор <> для объединения соответствующих выходных значений или любой другой монады.

join является фундаментальным свойством монад и является операцией, которая делает ее значительно более сильной, чем функтор или аппликативный функтор. Функторы могут быть сопоставлены, аппликативные могут быть последовательностями, монады могут быть объединены. Категорически, монада часто определяется как join и return. Так получилось, что в Haskell нам удобнее определять его в терминах return, (>>=) и fmap, но эти два определения были доказаны как синонимы.

Ответ 3

Интуиция о join заключается в том, что в нее вложено 2 контейнера..e.g

join [[1]] => [1]
join (Just (Just 1)) => 1
join (a christmas tree decorated with small cristmas tree) => a cristmas tree

и т.д.

Теперь, как вы можете присоединиться к функциям? Фактически функции, можно рассматривать как контейнер. Например, если вы посмотрите на таблицу хешей. Вы даете ключ, и получаете значение (или нет). Это функция key -> value (или если вы предпочитаете key -> Maybe value). Итак, как бы вы присоединились к 2 HashMap?

Скажем, у меня есть (в стиле python) h={"a": {"a": 1, "b": 2}, "b" : {"a" : 10, "b" : 20 }}, как я могу присоединиться к нему, или если вы предпочитаете его сгладить? Учитывая "a" какое значение я должен получить? h["a"] дает мне {"a":1, "b":2}. Единственное, что я могу сделать с этим, - это снова найти "а" в этом новом значении, которое дает мне 1. Следовательно, join h равно {"a":1, "b":20}.

То же самое для функции.