Ответ 1
Это, вероятно, не тот ответ, который вы ищете, но здесь вы все равно:
Очень извращенный способ взглянуть на монады и коллеги.
Один из способов взглянуть на абстрактные понятия, подобные этим, - связать их с базовыми понятиями, такими как обычные операции обработки списка. Тогда вы могли бы сказать, что
- Категория обобщает операцию
(.)
. - Моноид обобщает операцию
(++)
. - Функтор обобщает операцию
map
. - Аппликативный функтор обобщает операцию
zip
(илиzipWith
). - Монада обобщает операцию
concat
.
A Категория
Категория состоит из набора (или класса) объектов и пучка стрелок, каждая из которых соединяет два объекта. Кроме того, для каждого объекта должна быть стрелка идентификации, связывающая этот объект с самим собой. Кроме того, если есть одна стрелка (f
), которая заканчивается на объекте, а другая (g
), которая начинается с одного и того же объекта, тогда также должна быть составная стрелка, называемая g . f
.
В Haskell это моделируется как класс типа, который представляет категорию типов Haskell как объекты.
class Category cat where
id :: cat a a
(.) :: cat b c -> cat a b -> cat a c
Основными примерами категории являются функции. Каждая функция соединяет два типа, для всех типов существует функция id :: a -> a
, которая соединяет тип (и значение) с самим собой. Состав функций - обычная функциональная композиция.
Короче, категориями в базе Haskell являются вещи, которые ведут себя как функции, т.е. вы можете ставить один за другим с обобщенной версией (.)
.
Моноид
Моноид - это набор с единичным элементом и ассоциативная операция. Это моделируется в Haskell как:
class Monoid a where
mempty :: a
mappend :: a -> a -> a
Общие примеры моноидов включают:
- множество целых чисел, элемент 0 и операция
(+)
. - множество положительных чисел, элемент 1 и операция
(*)
. - набор всех списков, пустой список
[]
и операция(++)
.
Они моделируются в Haskell как
newtype Sum a = Sum {getSum :: a}
instance (Num a) => Monoid (Sum a) where
mempty = Sum 0
mappend (Sum a) (Sum b) = Sum (a + b)
instance Monoid [a] where
mempty = []
mappend = (++)
Моноиды используются для "комбинирования" и накопления вещей. Например, функция mconcat :: Monoid a => [a] -> a
может использоваться для сокращения списка сумм до одной суммы или вложенного списка в плоский список. Рассмотрим это как своего рода обобщение операций (++)
или (+)
, которые каким-то образом "объединяют" две вещи.
Функтор
Функтор в Haskell - это вещь, которая прямо непосредственно обобщает операцию map :: (a->b) -> [a] -> [b]
. Вместо сопоставления над списком он отображает некоторую структуру, такую как список, двоичное дерево или даже операцию ввода-вывода. Функторы моделируются следующим образом:
class Functor f where
fmap :: (a->b) -> f a -> f b
Сравните это с определением нормальной функции map
.
Аппликативный функтор
Аппликативные функторы можно рассматривать как вещи с обобщенной операцией zipWith
. Функторы отображают по общим структурам по одному в то время, но с аппликативным функтором вы можете объединить две или несколько структур. Для простейшего примера вы можете использовать аппликаторы для zip вместе целых чисел внутри типа Maybe
:
pure (+) <*> Just 1 <*> Just 2 -- gives Just 3
Обратите внимание, что структура может повлиять на результат, например:
pure (+) <*> Nothing <*> Just 2 -- gives Nothing
Сравните это с обычной функцией zipWith
:
zipWith (+) [1] [2]
Вместо просто списков аппликативная работа работает для всех видов структур. Кроме того, умный трюк с pure
и (<*>)
обобщает zipping для работы с любым количеством аргументов. Чтобы увидеть, как это работает, проверьте следующие типы, сохранив при этом концепцию частично прикладных функций:
instance (Functor f) => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
Обратите внимание также на сходство между fmap
и (<*>)
.
Монада
Монады часто используются для моделирования различных вычислительных контекстов, таких как недетерминированные или побочные эффекты. Поскольку слишком много учебников по монадам, я просто рекомендую лучший вариант вместо того, чтобы писать еще один.
В связи с обычными функциями обработки списка, монады обобщают функцию concat :: [[a]] -> [a]
для работы со многими другими типами структур помимо списков. В качестве простого примера монадическая операция join
может использоваться для выравнивания вложенных значений Maybe
:
join (Just (Just 42)) -- gives Just 42
join (Just (Nothing)) -- gives Nothing
Как это связано с использованием Monads в качестве средства структурирования вычислений? Рассмотрим пример игрушек, в котором вы делаете два последовательных запроса из некоторой базы данных. Первый запрос возвращает вам некоторое ключевое значение, с которым вы хотите выполнить другой поиск. Проблема здесь в том, что первое значение обернуто внутри Maybe
, поэтому вы не можете напрямую запрашивать это. Вместо этого, возможно, это Functor
, вместо этого вместо fmap
возвращаемое значение с новым запросом. Это даст вам два вложенных значения Maybe
, как указано выше. Другой запрос приведет к трем слоям Maybe
s. Это было бы довольно сложно запрограммировать, но монадический join
дает вам способ сгладить эту структуру и работать только с одним уровнем Maybe
s.
(Я думаю, что я буду редактировать этот пост много, прежде чем это будет иметь смысл.)