Хто перший сказав таке?
Монада - це просто моноїд у категорії ендофандерів, у чому проблема?
І на менш важливу увагу, чи це правда, і якщо так, ви можете дати пояснення (сподіваємось, таке, яке може зрозуміти той, хто не має багато досвіду Haskell)?
Хто перший сказав таке?
Монада - це просто моноїд у категорії ендофандерів, у чому проблема?
І на менш важливу увагу, чи це правда, і якщо так, ви можете дати пояснення (сподіваємось, таке, яке може зрозуміти той, хто не має багато досвіду Haskell)?
Відповіді:
Саме це фразоване слово Джеймса Ірі із його дуже захоплюючої короткої, неповної та переважно неправильної історії мов програмування , в якій він вигадано приписує це Філіпу Вадлеру.
Оригінальна цитата - з Сандер Мак Лейн в категорії «Робочий математик» , один із основоположних текстів Теорії категорій. Ось це в контексті , який, мабуть, найкраще місце, щоб дізнатися, що саме це означає.
Але я візьму колю. Оригінальне речення таке:
Все сказане, монада в X - це просто моноїд у категорії ендофункторів X, причому продукт × замінений складом ендофункторів та одиницею, встановленою ідентифікаційним ендофунктором.
X ось категорія. Endofunctors функтори з категорії в собі (що, як правило , все Functor -е, наскільки функціональні програмісти стурбовані, так як вони в основному має справу тільки з однією категорією; категорія типів - але я відволікся). Але ви можете собі уявити ще одну категорію, яка є категорією "ендофайнерів на X ". Це категорія, в якій об'єкти є ендофаунерами, а морфізми - природними перетвореннями.
І з цих ендофанконів деякі з них можуть бути монадами. Які з них монади? Саме ті, які є моноїдними в певному сенсі. Замість того, щоб конкретизувати точне відображення від монад до моноїдів (оскільки Mac Lane робить це набагато краще, ніж я міг би сподіватися), я просто поставлю їх відповідні визначення поряд і дозволю вам порівняти:
* -> *з Functorекземпляром)joinу Haskell)returnу Haskell)Трохи примружившись, ви, можливо, зможете побачити, що обидва ці визначення є примірниками одного абстрактного поняття .
Sце тип, все, що ви можете зробити при написанні функції, f :: () -> Sце вибрати якийсь певний тип типу S("елемент", якщо ви хочете) і повернути це ... вам не було надано реальної інформації з аргументом, тому немає можливості змінити поведінку функції. Отже, fповинна бути постійною функцією, яка щоразу повертає те саме. ()("Одиниця") є кінцевим об'єктом категорії Hask , і не випадково існує саме 1 ( нерозбіжне ) значення, яке його населяє.
Інтуїтивно, я думаю, що те, що говорить фантазійний математичний словник, це:
Моноїд являє собою набір об'єктів, а також спосіб їх об'єднання. Добре відомими моноїдами є:
Є і більш складні приклади.
Крім того, у кожного моноїда є ідентичність , яка є тим елементом "без відключення", який не має ефекту, коли ви поєднуєте його з чимось іншим:
Нарешті, моноїд повинен бути асоціативним . (ви можете зменшити довгий рядок комбінацій будь-коли, доки не захочете, якщо ви не зміните ліво-право на порядок об'єктів) Додавання нормально ((5 + 3) +1 == 5+ (3+ 1)), але віднімання не ((5-3) -1! = 5- (3-1)).
Тепер розглянемо особливий вид набору та особливий спосіб поєднання предметів.
Припустимо, ваш набір містить об'єкти особливого виду: функції . І ці функції мають цікавий підпис: вони не несуть числа до чисел або рядків до рядків. Натомість кожна функція несе число до списку чисел у двоступеневому процесі.
Приклади:
Також наш спосіб поєднання функцій особливий. Простим способом поєднання функції є композиція : Візьмемо наші приклади вище та складемо кожну функцію із себе:
Не вникаючи занадто багато в теорію типів, справа в тому, що ви можете об'єднати два цілих числа, щоб отримати ціле число, але ви не завжди можете скласти дві функції і отримати функцію одного типу. (Функції з типом a -> a складатимуть, але a-> [a] не буде.)
Отже, давайте визначимо інший спосіб поєднання функцій. Коли ми поєднуємо дві ці функції, ми не хочемо "подвоювати" результати.
Ось що ми робимо. Коли ми хочемо поєднати дві функції F і G, ми слідуємо за цим процесом (званий зв'язуванням ):
Повернувшись до наших прикладів, давайте поєднаємо (прив’яжемо) функцію до себе за допомогою цього нового способу "прив'язки" функцій:
Цей більш складний спосіб поєднання функцій є асоціативним (випливає з того, наскільки склад функції асоціативний, коли ви не займаєтесь вигадливими упаковками).
Зв’язуючи все це разом,
Існує маса способів "обернути" результати. Ви можете скласти список, набір або відкинути всі, крім першого результату, зазначаючи, що результатів немає, приєднати кошик стану, надрукувати повідомлення журналу тощо тощо.
Я трохи розгубився з визначеннями, сподіваючись інтуїтивно зрозуміти істотне уявлення.
Я трохи спростив речі, наполягаючи на тому, що наша монада працює над функціями типу a -> [a] . Насправді монади працюють над функціями типу a -> mb , але узагальнення - це вид технічної деталі, який не є основним розумінням.
a -> [b]і c -> [d](це можна зробити лише якщо b= c), це не зовсім описує моноїд. Це фактично описана вами операція вирівнювання, а не композиція функції, яка є "моноїдним оператором".
a -> [a], це буде моноїдом (тому що ви б зменшили категорію Kleisli до одного об'єкта та будь-яку категорію лише одного об'єкта за визначенням є моноїдом!), але він не зафіксував би всю спільність монади.
По-перше, розширення та бібліотеки, які ми будемо використовувати:
{-# LANGUAGE RankNTypes, TypeOperators #-}
import Control.Monad (join)
З них RankNTypes- єдиний, який абсолютно важливий для наведеного нижче. Я одного разу написав пояснення, RankNTypesщо деякі люди здаються корисними , тому я посилаюся на це.
Цитуючи відмінну відповідь Тома Кроккета , ми маємо:
Монада - це ...
- Ендофунктор, T: X -> X
- Природне перетворення, μ: T × T -> T , де × означає функторний склад
- Природне перетворення η: I -> T , де I - тотожний ендофактор на X
... що відповідають цим законам:
- μ (μ (T × T) × T)) = μ (T × μ (T × T))
- μ (η (T)) = T = μ (T (η))
Як ми переводимо це на код Haskell? Що ж, почнемо з поняття природного перетворення :
-- | A natural transformations between two 'Functor' instances. Law:
--
-- > fmap f . eta g == eta g . fmap f
--
-- Neat fact: the type system actually guarantees this law.
--
newtype f :-> g =
Natural { eta :: forall x. f x -> g x }
Тип формі f :-> gаналогічний типу функції, але замість того , щоб думати про нього , як функції між двома типами (у вигляді *), думати про нього , як морфізм між двома функторів (кожен з роду * -> *). Приклади:
listToMaybe :: [] :-> Maybe
listToMaybe = Natural go
where go [] = Nothing
go (x:_) = Just x
maybeToList :: Maybe :-> []
maybeToList = Natural go
where go Nothing = []
go (Just x) = [x]
reverse' :: [] :-> []
reverse' = Natural reverse
В основному, в Хаскеллі природні перетворення - це функції від якогось типу f xдо іншого типу, g xтакі, що xзмінна типу є "недоступною" для абонента. Так, наприклад, sort :: Ord a => [a] -> [a]не можна перетворитись на природне перетворення, оскільки це "прискіпливо" щодо того, для яких типів ми можемо ініціювати a. Один з інтуїтивно зрозумілих способів, як я часто думаю, це такий:
Тепер, не виходячи з цього, давайте займемося пунктами визначення.
Перший пункт - "ендофунктор, T: X -> X ". Що ж, кожен Functorз Haskell є ендофанктором у тому, що люди називають "категорією Hask", об'єктами якого є типи Haskell (у своєму роді *) і чиї морфізми є функціями Haskell. Це звучить як складне твердження, але насправді дуже тривіальне. Все, що означає, що a Functor f :: * -> *дає вам засоби побудови типу f a :: *для будь-якої a :: *і функції fmap f :: f a -> f bз будь-якої f :: a -> b, і що вони підкоряються законам функтора.
Другий пункт: Identityфунктор в Haskell (який поставляється з Платформою, тому ви можете просто імпортувати його) визначається таким чином:
newtype Identity a = Identity { runIdentity :: a }
instance Functor Identity where
fmap f (Identity a) = Identity (f a)
Отже, природне перетворення η: I -> T з визначення Тома Кроккета може бути записане таким чином для будь-якого Monadвипадку t:
return' :: Monad t => Identity :-> t
return' = Natural (return . runIdentity)
Третій пункт: Склад двох функторів у Haskell можна визначити таким чином (що також поставляється з Платформою):
newtype Compose f g a = Compose { getCompose :: f (g a) }
-- | The composition of two 'Functor's is also a 'Functor'.
instance (Functor f, Functor g) => Functor (Compose f g) where
fmap f (Compose fga) = Compose (fmap (fmap f) fga)
Тож природне перетворення μ: T × T -> T з визначення Тома Крокетта можна записати так:
join' :: Monad t => Compose t t :-> t
join' = Natural (join . getCompose)
Твердження, що це моноїд у категорії ендофункторів, означає, що Compose(частково застосовано лише до перших двох його параметрів) є асоціативним, і Identityце його елемент ідентичності. Тобто, мають місце такі ізоморфізми:
Compose f (Compose g h) ~= Compose (Compose f g) hCompose f Identity ~= fCompose Identity g ~= gЦе дуже легко довести , бо Composeі Identityобидва визначені як newtype, і Haskell звіти визначають семантику newtypeяк ізоморфізм між типом визначається і тип аргументу newtypeконструктора «и даних. Так, наприклад, докажемо Compose f Identity ~= f:
Compose f Identity a
~= f (Identity a) -- newtype Compose f g a = Compose (f (g a))
~= f a -- newtype Identity a = Identity a
Q.E.D.
Naturalновотипі я не можу зрозуміти, що (Functor f, Functor g)створює обмеження. Чи можете ви пояснити?
Functorобмеження, оскільки вони не здаються потрібними. Якщо ви не згодні, тоді не соромтесь додати їх назад.
joinвизначено. І joinце проекційний морфізм. Але я не впевнений.
Примітка: Ні, це неправда. У якийсь момент був коментар цієї відповіді від самого Дана Піпоні, який сказав, що причина і наслідок тут були прямо протилежні, що він написав свою статтю у відповідь на запит Джеймса Ірі. Але, здається, його зняли, можливо, якийсь нав'язливий приналежність.
Нижче моя оригінальна відповідь.
Цілком можливо, що Ірі прочитала « Моноїди до монадів» , посаду, в якій Дан Піпоні (сигфпе) виводить монади з моноїдів у Хаскеллі, багато обговорюючи теорію категорій і чітко згадуючи про «категорію ендофунторів на Хаскі ». У будь-якому випадку, кожен, хто замислиться про те, що означає монада бути моноїдом у категорії ендофайнерів, може отримати користь від того, щоб прочитати це виведення.
:-).
Я прийшов на цю посаду шляхом кращого розуміння висновку сумнозвісної цитати з Теорії категорій Мака Лейн для працюючого математика .
Описуючи, що щось таке, часто так само корисно описувати те, що це не так.
Той факт, що Mac Lane використовує опис для опису монади, може означати, що він описує щось унікальне для монад. Ведміть мене. Щоб розвинути більш широке розуміння твердження, я вважаю, що йому слід чітко пояснити, що він не є описує те, що є унікальним для монад; у заяві однаково описуються додаткові та стрілки серед інших. З тієї ж причини ми можемо мати два моноїди на Int (сума та продукт), у нас може бути декілька моноїдів на X у категорії ендофункторів. Але подібності є ще більше.
І Monad, і Applicative відповідають критеріям:
(наприклад, день у день Tree a -> List b, але в категорії Tree -> List)
Tree -> Listтільки List -> List.У операторі використовується "Категорія ...". Це визначає сферу твердження. Як приклад, категорія Functor описує сферу дії f * -> g *, тобто Any functor -> Any functor, наприклад, Tree * -> List *абоTree * -> Tree * .
Що категоричне твердження не вказує, описує, де все і все дозволено .
У цьому випадку всередині функторів * -> *ака a -> bне вказується, що означає Anything -> Anything including Anything else. Коли моя фантазія стрибає до Int -> String, вона також включає Integer -> Maybe Intабо навіть Maybe Double -> Either String Intкуди a :: Maybe Double; b :: Either String Int.
Отже, твердження складається разом так:
:: f a -> g b(тобто будь-який параметризований тип до будь-якого параметру):: f a -> f b(тобто будь-який один параметризований тип до того ж параметризованого типу) ... сказано по-різному,Отже, звідки сила цієї конструкції? Щоб оцінити повну динаміку, мені потрібно було зрозуміти, що типові малюнки моноїда (єдиного об’єкта з тим, що схоже на стрілку ідентичності, :: single object -> single object) не ілюструють, що мені дозволяється використовувати стрілку, параметризовану на будь-яку кількість моноїдних значень, від об'єкта одного типу, дозволеного в Monoid. Визначення еквівалентності стрілки endo, ~ ідентичності ігнорує значення типу функтора, а також тип і значення самого внутрішнього шару "корисного навантаження". Таким чином, еквівалентність повертається trueв будь-якій ситуації, коли функціональні типи збігаються (наприклад, Nothing -> Just * -> Nothingеквівалентні Just * -> Just * -> Just *тому, що вони обидва Maybe -> Maybe -> Maybe).
Бічна панель: ~ зовні є концептуальною, але є найбільш лівим символом у f a. Він також описує, що читає "Haskell" спочатку (велика картина); тож тип "зовні" стосовно значення типу. Взаємозв'язок між шарами (ланцюжком посилань) у програмуванні непросто пов'язати у категорії. Категорія набору використовується для опису типів (Int, Strings, можливо Int тощо), що включає категорію Functor (параметризовані типи). Опорний ланцюг: Тип функціонера, Значення функціоналу (елементи набору цього функтора, наприклад, Нічого, Просто), і, в свою чергу, все інше, на яке вказує значення кожного функтора. У категорії відносини описуються по-різному, наприклад, return :: a -> m aвважається природним перетворенням від одного Функтора до Іншого Функтора, відмінним від усього, що згадувалося до цього часу.
Повертаючись до основної нитки, в цілому для будь-якого визначеного тензорного добутку та нейтрального значення, заява закінчується описом дивовижно потужної обчислювальної конструкції, народженої з його парадоксальної структури:
:: List); статичнийfoldщо нічого не говорить про корисне навантаження)У Хаскеллі важливим є уточнення застосовності твердження. Потужність і універсальність цієї конструкції, не має абсолютно нічого спільного з монадой по собі . Іншими словами, конструкція не покладається на те, що робить монаду унікальною.
Намагаючись розібратися, чи потрібно будувати код із спільним контекстом для підтримки обчислень, які залежать один від одного, порівняно з обчисленнями, які можна виконувати паралельно, це сумнозвісне твердження, настільки, наскільки воно описує, не є контрастом між вибором Додаток, стрілки та монади, а це опис того, наскільки вони однакові. Що стосується рішення, заява суперечлива.
Це часто неправильно розуміється. Твердження далі описується join :: m (m a) -> m aяк тензорний добуток для моноїдного ендофунктора. Однак це не чітко визначає, як у контексті цього твердження (<*>)також можна було вибрати. Це справді є прикладом шести / півдюжини. Логіка поєднання значень точно однакова; один і той же вхід генерує однаковий вихід з кожного (на відміну від моноїдів Sum та Product для Int, оскільки вони створюють різні результати при поєднанні Ints).
Отже, підводячи підсумки: Моноїд у категорії ендофайнерів описує:
~t :: m * -> m * -> m *
and a neutral value for m *
(<*>)і (>>=)обидва забезпечують одночасний доступ до двох mзначень, щоб обчислити єдине повернене значення. Логіка, яка використовується для обчислення повернутого значення, точно така ж. Якби не різні форми функцій, які вони параметризують ( f :: a -> bпроти k :: a -> m b) та положення параметра з тим самим типом повернення обчислення (тобто a -> b -> bпроти b -> a -> bкожної відповідно), я підозрюю, що ми могли б параметризувати моноїдну логіку, тензорний добуток для повторного використання в обох визначеннях. Як вправу зробити точку, спробуйте виконати ~t, і ви закінчите (<*>)і (>>=)залежно від того, як ви вирішите це визначити forall a b.
Якщо мій останній пункт є як мінімум концептуально правдивим, то він пояснює точну та єдину обчислювальну різницю між додатком та монадою: функції, які вони параметризують. Іншими словами, різниця є зовнішньою для реалізації класів цих типів.
На закінчення, на моєму власному досвіді, сумнозвісна цитата Мак-Лейн забезпечила чудовий мемо «гото», який я можу посилатись, перебуваючи в дорозі по категорії, щоб краще зрозуміти ідіоми, які використовуються в Haskell. Йому вдалося захопити сферу потужної обчислювальної спроможності, яка стала чудово доступною в Haskell.
Однак є іронія в тому, як я вперше неправильно зрозумів застосовність твердження поза монадою, і те, що я сподіваюсь, передано тут. Все, що він описує, виявляється подібним між Applicative та Monads (і Стрілками серед інших). Те, що вона не говорить, - це саме невелика, але корисна відмінність між ними.
- Е
Відповіді тут відмінно справляються із визначенням моноїдів та монад, однак, вони все ще не відповідають на питання:
І на менш важливу увагу, чи це правда, і якщо так, ви можете дати пояснення (сподіваємось, таке, яке може зрозуміти той, хто не має багато досвіду Haskell)?
Суть речовини, якої тут бракує, полягає в різному понятті «моноїд», так званій категоризації, точніше - моноїді в моноїдальній категорії. На жаль, книга Мака Лейна сама робить це дуже заплутаним :
За всіма словами, монада в
X- це просто моноїд у категорії ендофункторівX, при цьому продукт×замінюється складом ендофункторів та блоком, встановленим ідентифікаційним ендофунктором.
Чому це заплутано? Тому що це не визначає, що таке "моноїд у категорії ендофайнерів" X. Натомість це речення пропонує взяти моноїд всередині набору всіх ендофайнерів разом із композицією функтора як бінарну операцію та функціонер ідентичності як моноїдну одиницю. Це прекрасно працює і перетворює в моноїд будь-яку підмножину ендофункторів, що містить функцію ідентифікатора і закрита під функторною композицією.
І все-таки це не правильне тлумачення, яке книга не дає зрозуміти на цьому етапі. Монада f- це нерухомий ендофунктор, а не підмножина ендофункторів, закритих під композицію. Загальна будівництво повинні використовувати fдля генерації моноїд, приймаючи безліч всіх k-кратноє композицій f^k = f(f(...))з fз самими собою, в тому числі , k=0що відповідає ідентичності f^0 = id. І тепер сукупність Sусіх цих повноважень для всіх k>=0справді є моноїдом "з продуктом × заміненим складом ендофункторів та одиницею, встановленою ідентифікаційним ендофунктором".
І ще:
Sможна визначити для будь-якого функтора fабо навіть буквально для будь- якої самодозвілля X. Це моноїд, породжений f.Sзадана композицією функтора та ідентифікатором функтора, не має нічого спільного з fтим, щоб бути монадою чи бути нею.А щоб зробити речі більш заплутаними, визначення "моноїд у моноїдальній категорії" з’являється пізніше в книзі, як ви можете бачити зі змісту . І все ж розуміння цього поняття є абсолютно критичним для розуміння зв'язку з монадами.
Переходячи до глави VII на моноїд (який приходить пізніше , ніж глава VI на монадах), ми знаходимо визначення так званої суворої категорії моноідальной як потрійні (B, *, e), де Bкатегорію, *: B x B-> Bв біфунктора (функтор по відношенню до кожного компоненту з іншим компонентом фіксованого ) і eє одиничним об'єктом B, що відповідає законам асоціативності та одиниці:
(a * b) * c = a * (b * c)
a * e = e * a = a
для будь-яких об'єктів a,b,cз B, і того ж тотожності для будь-яких морфізма a,b,cз eзамінено id_e, тотожним морфізма e. Зараз повчальним Bє зауваження, що в нашому випадку, де знаходиться категорія ендофанкторів Xіз природними перетвореннями, як морфізми, *композиція функтора та функція eідентичності, всі ці закони задовольняються, як це можна безпосередньо перевірити.
У книзі йдеться про визначення «розслабленої» моноїдальної категорії , де закони містять лише модулі певних фіксованих природних перетворень, що задовольняють так звані когерентні відносини , що, однак, не важливо для наших випадків категорій ендофактора.
Нарешті, у розділі 3 "Моноїди" глави VII подано фактичне визначення:
Моноїд
cв моноїдній категорії(B, *, e)- це об'єктBз двома стрілками (морфізми)
mu: c * c -> c
nu: e -> c
роблячи 3 діаграми комутативними. Нагадаємо, що в нашому випадку це морфізми в категорії ендофаніків, які є природними перетвореннями, що відповідають точно joinі returnдля монади. Зв'язок стає ще більш очевидним , коли ми робимо складу *більш явним, замінивши c * cна c^2, де cнаша монада.
Нарешті, зауважте, що 3 комутативні діаграми (у визначенні моноїд у моноїдальній категорії) написані для загальних (не строгих) моноїдних категорій, тоді як у нашому випадку всі природні перетворення, що виникають у складі моноїдної категорії, є насправді тотожностями. Це зробить діаграми абсолютно такими ж, як і у визначенні монади, зробивши кореспонденцію повною.
Підсумовуючи це, будь-яка монада за визначенням є ендофунктором, отже, об'єктом у категорії ендофункторів, де монадики joinта returnоператори задовольняють визначенню моноїду у цій конкретній (суворій) моноїдальній категорії . І навпаки, будь-який моноїд у моноїдній категорії ендофайнерів за визначенням є потрійним, (c, mu, nu)що складається з предмета та двох стріл, наприклад, природних перетворень у нашому випадку, що відповідають тим же законам, що й монада.
Нарешті, зазначимо ключову різницю між (класичними) моноїдами та більш загальними моноїдами в моноїдних категоріях. Дві стрілки muі nuвище вже не є бінарною операцією та одиницею в наборі. Натомість у вас є один фіксований ендофактор c. Композиція функтора *та сам функціонер ідентичності не забезпечують повну структуру, необхідну монаді, незважаючи на це заплутане зауваження в книзі.
Інший підхід був би порівняти зі стандартним моноїд Cвсіх самостійних відображень безлічі A, де бінарна операція є композиція, яка може бути помічена , щоб відобразити стандартний декартовій твір C x Cв C. Переходячи до категоризованого моноїда, ми заміняємо декартовий продукт xна композицію функтора *, і двійкова операція замінюється природним перетворенням muз
c * cв c, тобто сукупність joinоператорів
join: c(c(T))->c(T)
для кожного об'єкта T(тип програмування). А елементи ідентичності в класичних моноїдах, які можна ідентифікувати із зображеннями карт із фіксованого одноточкового набору, замінюють колекцією returnоператорів
return: T->c(T)
Але зараз більше немає декартових продуктів, тому немає пар елементів і, отже, немає двійкових операцій.