Що таке вільні монади?


368

Я бачив цей термін Free Монада спливав кожен в даний час , і потім в протягом деякого часу, але кожен раз здається , що використання / обговорювати їх , не даючи пояснення того , що вони є. Отже: що таке вільні монади? (Я б сказав, що я знайомий з монадами та основами Хаскелла, але знаю лише дуже грубі знання теорії категорій.)


12
Досить вдале пояснення тут haskellforall.com/2012/06/…
Роджер Ліндсьйо

19
@Roger - це така сторінка, яка привела мене сюди. Для мене цей приклад визначає монадний екземпляр для типу з назвою "Безкоштовно", і це все.
Девід

Відповіді:


295

Відповідь Едварда Кметта, очевидно, чудова. Але, це трохи технічно. Ось, можливо, більш доступне пояснення.

Вільні монади - це лише загальний спосіб перетворення функторів у монади. Тобто, якщо будь-який функтор f Free f- це монада. Це було б не дуже корисно, якщо ви не отримаєте пару функцій

liftFree :: Functor f => f a -> Free f a
foldFree :: Functor f => (f r -> r) -> Free f r -> r

перший з них дозволяє вам "потрапити в" монаду, а другий дає вам спосіб "вибратися" з неї.

Більш загально, якщо X є Y з деякими додатковими речовинами P, то "вільний X" - це спосіб переходу з Y в X, не отримуючи нічого зайвого.

Приклади: моноїд (X) - це набір (Y) з додатковою структурою (P), який в основному говорить, що він має операцію (можна думати про додавання) та деяку тотожність (як нуль).

Тому

class Monoid m where
   mempty  :: m
   mappend :: m -> m -> m

Тепер ми всі знаємо списки

data [a] = [] | a : [a]

Ну, враховуючи будь-який тип, tми знаємо, що [t]це моноїд

instance Monoid [t] where
  mempty   = []
  mappend = (++)

і тому списки є "вільним моноїдом" над наборами (або у типах Haskell).

Гаразд, так що вільні монади - це та сама ідея. Ми беремо функтор і повертаємо монаду. Насправді, оскільки монади можуть розглядатися як моноїди в категорії ендофайнерів, визначення списку

data [a] = [] | a : [a]

дуже схоже на визначення вільних монад

data Free f a = Pure a | Roll (f (Free f a))

і Monadекземпляр має схожість з Monoidекземпляром для списків

--it needs to be a functor
instance Functor f => Functor (Free f) where
  fmap f (Pure a) = Pure (f a)
  fmap f (Roll x) = Roll (fmap (fmap f) x)

--this is the same thing as (++) basically
concatFree :: Functor f => Free f (Free f a) -> Free f a
concatFree (Pure x) = x
concatFree (Roll y) = Roll (fmap concatFree y)

instance Functor f => Monad (Free f) where
  return = Pure -- just like []
  x >>= f = concatFree (fmap f x)  --this is the standard concatMap definition of bind

Тепер ми отримуємо наші дві операції

-- this is essentially the same as \x -> [x]
liftFree :: Functor f => f a -> Free f a
liftFree x = Roll (fmap Pure x)

-- this is essentially the same as folding a list
foldFree :: Functor f => (f r -> r) -> Free f r -> r
foldFree _ (Pure a) = a
foldFree f (Roll x) = f (fmap (foldFree f) x)

12
Це може бути найкращим доступним поясненням "безкоштовного", якого я ще бачив. Особливо абзац, що починається з "Більш загально".
Джон Л

16
Я думаю, що цікаво подивитися Free f a = Pure a | Roll (f (Free f a))як Free f a = a + fa + ffa + ..., тобто "f застосовується в будь-яку кількість разів". Тоді concatFree(тобто join) приймається "f, застосовано будь-яку кількість разів до (f застосовано будь-яку кількість разів до a)" і згортає два вкладені програми в одне. І >>=приймає "f, застосовуючи будь-яку кількість разів до" і "як дістатися від a до (b з f застосовується будь-яку кількість разів)", і в основному застосовує останнє до a всередині колишнього і згортає вкладення. Тепер я сам це отримую!
jkff

1
в concatFreeосновному join?
rgrinberg

11
«Ось, можливо, більш доступне пояснення. […] Насправді, оскільки монади можна розглядати як моноїди в категорії ендофункторів, ... "Тим не менш, я думаю, що це дуже хороша відповідь.
Рууд

2
«Монади можна розглядати як моноїд в категорії ендо- функторів» <3 (ви повинні зв'язати з stackoverflow.com/a/3870310/1306877 , тому що кожен haskeller повинен знати про те , що посилання!)
TLO

418

Ось ще простіша відповідь: Монада - це те, що "обчислюється", коли монадичний контекст згортається join :: m (m a) -> m a(нагадуючи, що >>=можна визначити як x >>= y = join (fmap y x)). Ось так Monads переносить контекст через послідовний ланцюг обчислень: адже в кожній точці серії контекст попереднього виклику згортається з наступним.

А вільна монада задовольняє всі закони Монада, але не роблять яке - або руйнується (тобто обчислення). Він просто створює вкладені серії контекстів. Користувач, який створює таке вільне монадичне значення, несе відповідальність за те, щоб зробити щось із тими вкладеними контекстами, щоб значення такої композиції можна було відкласти до моменту створення монадичного значення.


8
Ваші абзаци є дійсно чудовим доповненням до поста Філіпа.
Девід

20
Мені дуже подобається ця відповідь.
danidiaz

5
Чи може вільна монада замінити клас типу Monad? Тобто, чи можу я написати програму, використовуючи лише повернення та зв'язування вільної монади, а потім приєднатись до результатів, використовуючи те, що я віддаю перевагу Mwybe чи List чи що завгодно, або навіть генерувати кілька монадичних поглядів однієї послідовності викликів прив’язаних / стислих функцій. Ігнорування дна та незнищення, тобто.
misterbee

2
Ця відповідь допомогла, але я думаю, це збентежило б мене, якби я не зустрівся «приєднатися» до курсу NICTA і прочитав haskellforall.com/2012/06/… . Тому для мене хитрість розуміння полягає в тому, щоб прочитати багато відповідей, поки воно не зануриться. (NICTA Довідка: github.com/NICTA/course/blob/master/src/Course/Bind.hs )
Мартін Каподічі

1
ця відповідь найкраща коли-небудь
Curycu

142

Вільна foo буває найпростішою справою, яка задовольняє всім законам "foo". Тобто це відповідає саме законам, необхідним для того, щоб бути дурнем і нічого зайвого.

Забудливий функтор - це той, хто «забуває» частину структури, переходячи від однієї категорії до іншої.

Дані функтори F : D -> C, і G : C -> D, ми говоримо F -| G, Fліворуч прилягають до G, або Gє правильним примиканням до Fкожного разу, коли a, b: F a -> bє ізоморфним до a -> G b, куди стрілки походять із відповідних категорій.

Формально вільний функтор залишається поруч із забудькучим функтором.

Вільний моноїд

Почнемо з більш простого прикладу - вільного моноїда.

Візьміть моноїд, який визначається деяким набором носіїв T, бінарна функція пом'яти пару елементів разом f :: T → T → T, і unit :: T, таким чином, що у вас є асоціативний закон, і закон ідентичності: f(unit,x) = x = f(x,unit).

Ви можете зробити функтор Uз категорії моноїд (де стрілка моноїд гомоморфізми, тобто, вони забезпечують їм карту unitв unitіншому моноїд, і що ви можете скласти до або після відображення на інший моноїд без зміни значення) в категорію наборів (де стрілки - це лише функціональні стрілки), які «забувають» про операцію unitі просто дарують вам набір операторів.

Потім ви можете визначити функтор Fз категорії наборів назад до категорії моноїдів, що залишилася суміжною з цим функтором. Цей функтор - це функтор, який відображає набір aв моноїд [a], де unit = []і mappend = (++).

Отже, для перегляду нашого прикладу в псевдо-Haskell:

U : Mon  Set -- is our forgetful functor
U (a,mappend,mempty) = a

F : Set  Mon -- is our free functor
F a = ([a],(++),[])

Тоді, щоб шоу Fбуло безкоштовним, нам потрібно продемонструвати, що воно залишається суміжним U, забудькуватому функтору, тобто, як ми згадували вище, нам потрібно показати, що

F a → b є ізоморфним для a → U b

Тепер згадаймо, що ціль Fзнаходиться в категорії Monмоноїдів, де стрілки є моноїдними гомоморфізмами, тому нам потрібно, щоб показати, що моноїдний гомоморфізм з [a] → bможе бути описаний саме функцією від a → b.

У Haskell ми називаємо сторону цього, що живе в Set(er, Haskкатегорія типів Haskell, на яку ми робимо вигляд, що встановлена), як раз foldMap, яка, коли спеціалізується Data.Foldableна списках, має тип Monoid m => (a → m) → [a] → m.

Є наслідки, які випливають із цього, як приєднання. Примітно, що якщо ви забудете, то надбудуйте безкоштовно, то забудьте знову, це так само, як ви колись забули, і ми можемо використовувати це для створення монадійного приєднання. оскільки UFUF~ U(FUF)~ UF, і ми можемо перейти у моноїдний гомоморфізм ідентичності від [a]до [a]через ізоморфізм, який визначає наше пристосування, отримуємо, що список ізоморфізму [a] → [a]є функцією типу a -> [a], і це просто повернення для списків.

Ви можете скласти все це безпосередньо, описуючи список у цих умовах за допомогою:

newtype List a = List (forall b. Monoid b => (a -> b) -> b)

Вільна Монада

То що таке Вільна Монада ?

Що ж, ми робимо те саме, що робили раніше, ми починаємо із забуваючого функтора U з категорії монад, де стріли - гомоморфізми монади, до категорії ендофайнерів, де стрілки - це природні перетворення, і шукаємо функтора, який залишився суміжним до цього.

Отже, як це стосується поняття вільної монади, як воно зазвичай використовується?

Знаючи, що щось є вільною монадою, Free fговорить вам, що давати гомоморфізм монади з боку Free f -> m- це те саме (ізоморфне), що і природне перетворення (гомоморфізм функтора) з f -> m. Пам'ятайте, що це F a -> bмає бути ізоморфно, щоб a -> U bF залишалося поруч із U. U, тут відображені монади до функторів.

F принаймні ізоморфний Freeтипу, який я використовую в своїй freeупаковці при злому.

Ми також могли б побудувати його за більш чіткою аналогією з кодом вище для вільного списку, визначаючи

class Algebra f x where
  phi :: f x -> x

newtype Free f a = Free (forall x. Algebra f x => (a -> x) -> x)

Cofree Comonads

Ми можемо побудувати щось подібне, подивившись праворуч на спокійний функтор, припустивши, що воно існує. Безфункціональний функтор - це просто / правильний примикання / до забудькуватого функтора, і за симетрією знати щось є кофрі-комода - це те саме, що знати, що давати гомоморфізм комонаду w -> Cofree f- це те саме, що давати природну трансформацію w -> f.


12
@PauloScardine, це нічого, про що ти не повинен турбуватися. На моє запитання випливало зацікавлення зрозуміти деяку розширену структуру даних і, можливо, зрозуміти, що є найсучаснішим у розвитку Haskell зараз - це ні в якому разі не є необхідним чи представником того, про що насправді пише Haskell. (І нахиливши голову, стає краще, коли ви знову пройдете етап навчання в ІО)
Девід

8
@PauloScardine Вищенаведена відповідь не потрібна для того, щоб продуктивно програмувати в Haskell, навіть з безкоштовними монадами. Насправді я б не рекомендував таким чином атакувати вільну монаду тому, хто не мав досвіду теорії категорій. Існує безліч способів поговорити про це з оперативної точки зору, і, як використовувати його, не занурюючись в теорію категорій. Однак мені неможливо відповісти на питання про те, звідки вони беруться, не занурившись у теорію. Безкоштовні конструкції є потужним інструментом теорії категорій, але для їх використання вам не потрібен цей фон.
Едвард КМЕТТ

18
@PauloScardine: Вам не потрібно точно обчислювати, щоб ефективно використовувати Haskell і навіть розуміти, що ви робите. Трохи дивно скаржитися, що "ця мова є математичною", коли зрілість - це просто додаткове добро, яке ви можете використовувати для розваги та отримання прибутку. Ви не розумієте ці речі на більшості імперативних мов. Чому б ти скаржився на додаткові послуги? Ви можете просто вибрати НЕ міркувати математично і підходити до нього так, як і до будь-якої іншої нової мови.
Сара

3
@Sarah: Я ще не побачив документацію або IRC-розмову про haskell, який не є важким для комп'ютерної теорії та термів обчислення лямбда.
Пауло Скардін

11
@PauloScardine це трохи відновлює, але на захист Haskell: подібні технічні речі стосуються всіх інших мов програмування, тільки що Haskell має таку приємну збірку, що люди можуть насправді насолоджуватися розмовою про них. Чому / як Х є монадою, цікаво багатьом людям, дискусії про стандарт IEEE з плаваючою точкою не мають; обидва випадки не мають значення для більшості людей, оскільки вони можуть просто використовувати результати.
Девід

72

Вільна монада (структура даних) відноситься до монади (класу) подібно до списку (структура даних) до моноїда (класу): це тривіальна реалізація, де згодом ви можете вирішити, як буде поєднуватися вміст.


Ви, мабуть, знаєте, що таке Монада, і що кожна Монада потребує конкретного (дотримання закону Монади) впровадження або fmap+ join+, returnабо bind+ return.

Припустимо, у вас є Functor (реалізація fmap), але решта залежить від значень та варіантів, зроблених під час виконання, що означає, що ви хочете мати можливість використовувати властивості Monad, але хочете вибирати функції Monad після цього.

Це можна зробити за допомогою вільної монади (структура даних), яка обертає функцію (тип) таким чином, що joinє швидше укладанням цих функторів, ніж скороченням.

Реальне returnі joinви хочете використовувати, тепер можна подати як параметри функції зменшення foldFree:

foldFree :: Functor f => (a -> b) -> (f b -> b) -> Free f a -> b
foldFree return join :: Monad m => Free m a -> m a

Для пояснення типів, ми можемо замінити Functor fз Monad mі bз (m a):

foldFree :: Monad m => (a -> (m a)) -> (m (m a) -> (m a)) -> Free m a -> (m a)

8
Ця відповідь дала мені враження, що я розумію, для чого вони можуть бути навіть корисні.
Девід

59

Вільна монада Haskell - це список факторів. Порівняйте:

data List a   = Nil    | Cons  a (List a  )

data Free f r = Pure r | Free (f (Free f r))

Pureє аналогом Nilі Freeє аналогом Cons. Безкоштовна монада зберігає список функторів замість списку значень. Технічно ви можете реалізувати вільні монади, використовуючи інший тип даних, але будь-яка реалізація повинна бути ізоморфною вище.

Ви використовуєте безкоштовні монади, коли вам потрібно абстрактне синтаксичне дерево. Основним функтором вільної монади є форма кожного кроку синтаксичного дерева.

Мій пост , з яким хтось уже пов’язаний, наводить кілька прикладів того, як будувати абстрактні синтаксичні дерева з вільними монадами


6
Я знаю, що ти просто робив аналогію, а не робив визначення, але вільна монада, безумовно, не аналогічна переліку функторів у будь-якому сенсі. Це набагато ближче до дерева функторів.
Том Елліс

6
Я стою біля своєї термінології. Наприклад, за допомогою мого пакунку з індексом ядро ​​ви можете визначити "вільне розуміння монади", яке поводиться так само, як монада списку, за винятком того, що ви прив'язуєте функторів замість значень. Вільна монада - це перелік функторів у тому сенсі, що якщо ви переведете всі поняття Haskell до категорії функторів, то списки стають вільними монадами. Справжнє дерево функторів тоді стає чимось зовсім іншим.
Габріель Гонсалес

4
Ви маєте рацію, що монада - це певна категоріфікація поняття моноїд, тому вільні монади є аналогом вільних моноїдів, тобто списків. Настільки ви правді правильні. Однак структура значення вільної монади не є списком. Це дерево, як я детально описую нижче .
Том Елліс

2
@TomEllis Технічно це лише дерево, якщо ваш базовий функтор - це функціональний продукт. Якщо у вас є базовий функтор, він більше нагадує машину стеку.
Габріель Гонсалес

21

Думаю, простий конкретний приклад допоможе. Припустимо, у нас є функтор

data F a = One a | Two a a | Two' a a | Three Int a a a

з очевидним fmap. Тоді Free F aтип дерев, листя яких мають тип aі чиї вузли позначені One, Two, Two'і Three. One-вузли мають одну дитину, Two- і Two'-вузли мають двох дітей, а Three-вузли мають трьох і також позначені міткою Int.

Free Fє монадою. returnкарти xна дерево, яке є лише листям зі значенням x. t >>= fрозглядає кожне з листя і замінює їх деревами. Коли лист має значення, yвін замінює цей лист деревом f y.

Діаграма робить це більш зрозумілим, але я не маю можливості легко накреслити його!


14
Ви, хлопці, говорите, що вільна монада набуває форму самого функтора. Отже, якщо функтор деревоподібний (продукти), вільна монада є деревоподібною; якщо він схожий на список (суми), вільна монада схожа на список; якщо це функціонально, вільна монада є функціональною; і т.д. Це для мене має сенс. Так що, як і у вільному моноїді, ви продовжуєте трактувати кожне застосування mappend як створення абсолютно нового елемента; у вільній монаді ви розглядаєте кожне застосування функтора як абсолютно новий елемент.
Bartosz Milewski

4
Навіть якщо функтор є "функтором суми", то отримана вільна монада все ще нагадує дерево. У вас є кілька типів вузла на вашому дереві: по одному для кожного компонента вашої суми. Якщо вашим "функтором суми" є X -> 1 + X, ви дійсно отримаєте список, який є лише виродженим сортом дерева.
Том Елліс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.