Есть ли какая-нибудь интуиция, чтобы понять, как объединить две функции в Monad?

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

С точки зрения системы типов (+) 7 :: Num a => a -> a можно рассматривать как Functor, (+) :: Num a => a -> a -> a можно рассматривать как Functor из 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

person Kamel    schedule 01.09.2015    source источник
comment
«(+) 7 :: Num a => a -> a можно рассматривать как _2 _...», пожалуйста, не говорите так. (7+) - это не функтор, это все равно что сказать, что атом углерода - это алмаз. Функтором является (Int ->), то есть конструктор типа, который принимает типы и генерирует функции из чисел в этот тип.   -  person leftaroundabout    schedule 01.09.2015
comment
Для функций join f = \a -> f a a. Таким образом join (+) = \a -> a + a.   -  person AJF    schedule 01.09.2015
comment
@leftaroundabout Разве (+) 7 :: Num a => a -> a не является функтором категории Num?   -  person Kamel    schedule 01.09.2015
comment
@Kamel: Num как категория? Фух, посмотрим. Его объектами будут числовые типы n, m, .... Морфизмами будут функции n -> m и т. Д. Итак, (7+) берет числовой тип и отображает его ... себе, достаточно справедливо. Но как он отображает морфизмы? Нет, боюсь, в этом нет никакого смысла.   -  person leftaroundabout    schedule 01.09.2015
comment
@leftaroundabout (+7) отображает морфизмы числового типа на другие морфизмы числового типа, например (+1) на (+8). Все функторы в Haskell отображают категорию в себя.   -  person Kamel    schedule 01.09.2015
comment
@Kamel: ну, вы правы, математически это похоже на функтор. (Довольно вырожденный, ум ...) В любом случае, это не функтор функции. Это не сопоставляет типы с собой. Что касается «все функторы в Haskell сопоставляют категорию с собой» - вы имеете в виду, что они эндофункторы на Hask; но это не значит, что они сопоставляют объекты / типы с собой. На самом деле это невозможно, потому что функторы являются конструкторами типов, а их фиксированные точки будут «бесконечными типами».   -  person leftaroundabout    schedule 02.09.2015
comment
@leftaroundabout Что касается функтора функции, вы имеете в виду построение типа :k * -> *?   -  person Kamel    schedule 02.09.2015
comment
@Kamel: * -> * - это отличительная черта любого функтора Haskell. Под функтором функции я имею в виду функторы формы (A -> ), то есть семейство функторов, которые определены instance Functor ((->) a).   -  person leftaroundabout    schedule 02.09.2015
comment
@leftaroundabout Спасибо! Это не функтор Haskell, хотя он и является функтором.   -  person Kamel    schedule 03.09.2015


Ответы (3)


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

Я бы сказал, что использование системы типов - отличный способ развить особую интуицию. Тип 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

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

person duplode    schedule 01.09.2015

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

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

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

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

person Silvio Mayolo    schedule 01.09.2015
comment
как + 1 читательская монада? Вы имеете в виду как-то концептуально или также на уровне шрифта? - person Erik Kaplun; 01.09.2015
comment
(+ 1) - это функция, которая принимает аргумент и добавляет его. Концептуально его можно рассматривать как значение, зависящее от значения, доступного только для чтения, которое ему еще не известно. (+ 1) можно рассматривать как целое число, которое мы не можем исследовать, пока не дадим ему среду. - person Silvio Mayolo; 01.09.2015
comment
так что это всего лишь концептуальная аналогия, а не факт уровня типа. - person Erik Kaplun; 01.09.2015
comment
@ErikAllik Это нечто большее. Хотя (->) r в буквальном смысле не то же самое, что Reader r, они изоморфны. Также (->) r является экземпляром MonadReader < / а>. Я бы посчитал это фактом уровня шрифта. - person duplode; 01.09.2015
comment
Единственный момент, в котором этот ответ мог бы быть более точным, - это такие отрывки, как [использование] функции как монады, и (+) будет монадой читателя внутри монады читателя. Такие значения, как (+), не являются монадами; это монадические ценности. Это конструкторы типов, такие как (->) r, которые являются монадами. См. Также комментарии leftaroundabout к заданному выше вопросу. - person duplode; 01.09.2015

Интуиция по поводу join такова, что это сжимает 2 контейнера в один. .например

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}. Единственное, что я могу с этим сделать, - это снова найти "a" в этом новом значении, что дает мне 1. Следовательно, join h равно {"a":1, "b":20}.

То же самое и с функцией.

person mb14    schedule 01.09.2015