Я очень запутался в этих трех концепциях.
Есть ли какие-нибудь простые примеры, иллюстрирующие различия между Category, Monoid и Monad?
Было бы очень полезно, если бы была иллюстрация этих абстрактных понятий.
Я очень запутался в этих трех концепциях.
Есть ли какие-нибудь простые примеры, иллюстрирующие различия между Category, Monoid и Monad?
Было бы очень полезно, если бы была иллюстрация этих абстрактных понятий.
Вероятно, это не тот ответ, который вы ищете, но все равно можете:
Один из способов взглянуть на подобные абстрактные концепции - связать их с базовыми концепциями, такими как обычные операции обработки списков. Тогда вы могли бы сказать это,
(.)
.(++)
.map
.zip
(или zipWith
).concat
.Категория состоит из набора (или класса) объектов и группы стрелок, каждая из которых соединяет два объекта. Кроме того, для каждого объекта должна быть стрелка идентификации, соединяющая этот объект с самим собой. Кроме того, если есть одна стрелка (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
Общие примеры моноидов включают:
(+)
.(*)
.[]
и операция (++)
.Они моделируются в 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
операцией. Функторы сопоставляются с общими структурами по одной, но с помощью функтора Applicative вы можете соединить вместе две или более структур. В простейшем примере вы можете использовать аппликативы для соединения двух целых чисел внутри типа Maybe
:
pure (+) <*> Just 1 <*> Just 2 -- gives Just 3
Обратите внимание, что структура может повлиять на результат, например:
pure (+) <*> Nothing <*> Just 2 -- gives Nothing
Сравните это с обычной функцией zipWith
:
zipWith (+) [1] [2]
Аппликатив работает не только со списками, но и со всеми видами структур. Вдобавок хитрый трюк с pure
и (<*>)
обобщает сжатие для работы с любым количеством аргументов. Чтобы увидеть, как это работает, изучите следующие типы, сохраняя при этом понятие частично применяемых функций:
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
Как это связано с использованием монад как средства структурирования вычислений? Рассмотрим игрушечный пример, в котором вы выполняете два последовательных запроса из некоторой базы данных. Первый запрос возвращает вам какое-то значение ключа, с которым вы хотите выполнить еще один поиск. Проблема здесь в том, что первое значение заключено в Maybe
, поэтому вы не можете запрашивать его напрямую. Вместо этого, поскольку это, возможно, Functor
, вы могли бы вместо этого fmap
возвращать значение с новым запросом. Это даст вам два вложенных Maybe
значения, как указано выше. Другой запрос приведет к трем уровням Maybe
. Это было бы довольно сложно запрограммировать, но монадический join
дает вам способ сгладить эту структуру и работать только с одним уровнем Maybe
.
(Думаю, я буду долго редактировать этот пост, прежде чем он приобретет хоть какой-то смысл ..)
mempty = 0
должно быть mempty = Sum 0
.
- person snak; 01.04.2013
Applicative
для []
- это не динамичный, а декартово произведение. Не очень полезная монада динамичных списков действительна только для списков постоянной длины, а ее join
занимает диагональ.
- person C. A. McCann; 01.04.2013
fzip :: (f a, f b) -> f (a,b)
, объединяющего два f
в один. (см. stackoverflow.com/a/15211856/849891).
- person Will Ness; 01.04.2013
Applicative
» означает экземпляр, который ведет себя как структурное пересечение, сопоставляя элементы из двух структур, которые имеют одинаковую позицию. Такой fzip
, реализованный с Applicative
, может не вести себя как zip
с другими экземплярами, очевидным примером является экземпляр списка по умолчанию, где fzip
даст полное декартово произведение.
- person C. A. McCann; 01.04.2013
Я думаю, что для понимания монад нужно поиграться с оператором связывания (>>=
). Сильно под влиянием [http://dev.stephendiehl.com/hask/#eightfold-path-to-monad-satori](Don читайте учебники по монаде.)
Моя маленькая пьеса такова:
По материалам http://www.haskellforall.com/2014/10/how-to-desugar-haskell-code.html
Prelude> f = getLine >>= \a -> putStrLn a
Prelude> f
abc
abc
Prelude>
и подписи:
Prelude> :t getLine
getLine :: IO String
Prelude> :t (\a -> putStrLn a)
(\a -> putStrLn a) :: String -> IO ()
Prelude> :t f
f :: IO ()
Результат: видны части подписи (>>=) :: Monad m => m a -> (a -> m b) -> m b
.
Адаптация из https://wiki.haskell.org/Simple_monad_examples
Prelude> g x = if (x == 0) then Nothing else Just (x + 1)
Prelude> Just 0 >>= g
Nothing
Prelude> Just 1 >>= g
Just 2
Результат: fail "zero"
is Nothing
... как описано в https://www.slideshare.net/ScottWlaschin/functional-design-patterns-devternity2018