Що означає "вуглегебра" в контексті програмування?


339

Я чував термін «вугілля» декілька разів у гуртках функціонального програмування та PLT, особливо коли мова йде про об’єкти, комони, лінзи тощо. Гуглінг цей термін дає сторінки, які дають математичний опис цих структур, що мені дуже незрозуміло. Чи може хто-небудь пояснити, будь ласка, що означають вуглегебри в контексті програмування, яке їх значення та як вони відносяться до об'єктів та комодів?


21
Чи можу я рекомендувати відмінну книгу Джеремі Гіббонса у ФП: patterninfp.wordpress.com та його цілком зрозумілу статтю "Розрахунок функціональних програм"? Вони обидва висвітлюють вугілля досить суворим способом (порівняно, наприклад, з повідомленням у блозі), але вони також є досить автономними для того, хто трохи знає Haskell.
Крістофер Мічінський

2
@KristopherMicinski, дуже цікаво. Дякую!
зниклий фактор

Відповіді:


474

Алгебри

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

Тож алгебра - це лише тип τз деякими функціями та тотожностями. Ці функції приймають різну кількість аргументів типу τі створюють τ: непротиповнені, всі вони схожі (τ, τ,…, τ) → τ. Вони також можуть мати "тотожність" - елементи τяких мають особливу поведінку з деякими функціями.

Найпростіший приклад цього - моноїд. Моноїд - це будь-який тип τз функцією mappend ∷ (τ, τ) → τта ідентичністю mzero ∷ τ. Інші приклади включають такі речі, як групи (які подібно до моноїдів, за винятком додаткової invert ∷ τ → τфункції), кільця, решітки тощо.

Усі функції функціонують, τале можуть мати різні арності. Ми можемо виписати це як τⁿ → τ, де τⁿкарти до набору n τ. Таким чином, має сенс думати про особистість як про те, τ⁰ → τде τ⁰тільки порожній кортеж (). Тож ми можемо реально спростити ідею алгебри: це просто якийсь тип з деякою кількістю функцій.

Алгебра - це просто поширена закономірність математики, яка "розроблена", як і ми з кодом. Люди помітили, що ціла купа цікавих речей - вищезгадані моноїди, групи, ґрати тощо - всі слідують подібній схемі, тому вони її абстрагували. Перевага робити це така ж, як і в програмуванні: це створює багаторазові докази та полегшує певні види міркувань.

F-алгебри

Однак з факторингом ми не зовсім готові. Поки ми маємо купу функцій τⁿ → τ. Насправді ми можемо зробити акуратний трюк, щоб об’єднати їх усіх в одну функцію. Зокрема, давайте подивимось на моноїди: у нас є mappend ∷ (τ, τ) → τі mempty ∷ () → τ. Ми можемо перетворити їх у єдину функцію, використовуючи тип суми - Either. Це виглядатиме так:

op  Monoid τ  Either (τ, τ) ()  τ
op (Left (a, b)) = mappend (a, b)
op (Right ())    = mempty

Фактично ми можемо використовувати це перетворення кілька разів , щоб об'єднати всі ті τⁿ → τфункції в один, для будь-якої алгебри. (Насправді, ми можемо зробити це для будь-якого числа функцій a → τ, b → τі так далі для будь-якого a, b,… .)

Це дозволяє нам говорити про алгебри як тип τз єдиною функцією від деякого безладу Eithers до одиниці τ. Для моноїд, цей безлад: Either (τ, τ) (); для груп (які мають додаткову τ → τоперацію), це: Either (Either (τ, τ) τ) (). Це різний тип для кожної різної структури. То що спільного для всіх цих типів? Найбільш очевидним є те, що всі вони є лише сумою продуктів - алгебраїчними типами даних. Наприклад, для моноїдів ми могли б створити тип аргументу моноїд, який працює для будь-якого моноїда τ:

data MonoidArgument τ = Mappend τ τ -- here τ τ is the same as (τ, τ)
                      | Mempty      -- here we can just leave the () out

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

Що ще особливого у всіх цих типах? Ну, вони всі Functors! Наприклад:

instance Functor MonoidArgument where
  fmap f (Mappend τ τ) = Mappend (f τ) (f τ)
  fmap f Mempty        = Mempty

Тож ми можемо ще більше узагальнити наше уявлення про алгебру. Це просто якийсь тип τз функцією f τ → τдля якогось функтора f. Насправді ми могли б записати це як тип класу:

class Functor f  Algebra f τ where
  op  f τ  τ

Це часто називають "F-алгеброю", оскільки це визначає функтор F. Якби ми могли частково застосувати типові класи, ми могли б визначити щось подібне class Monoid = Algebra MonoidArgument.

Вугілля

Тепер, сподіваємось, ви добре зрозуміли, що таке алгебра і як це просто узагальнення нормальних алгебраїчних структур. Отже, що таке F-вуглегебра? Ну, co означає, що це "подвійний" алгебри, тобто ми беремо алгебру і перегортаємо стрілки. У наведеному вище визначенні я бачу лише одну стрілку, тому я просто переверну:

class Functor f  CoAlgebra f τ where
  coop  τ  f τ

І ось це все! Тепер цей висновок може здатися трохи легковажним (хе). Це говорить вам про те, що таке вуглегебра, але насправді не дає зрозуміти, наскільки вона корисна чи чому нас хвилює. Я дістанусь до цього трохи, як тільки знайду чи придумаю хороший приклад чи два: P.

Класи та предмети

Прочитавши трохи, я думаю, що маю гарне уявлення про те, як використовувати вугілля для представлення класів та об’єктів. У нас є тип, Cякий містить усі можливі внутрішні стани об’єктів у класі; сам клас - це вуглегебра, над Cякою вказуються методи та властивості об’єктів.

Як показано на прикладі алгебри, якщо у нас є купа функцій, як a → τі b → τдля будь-яких a, b,…, ми можемо об'єднати їх у одну функцію, використовуючи Eitherтип суми. Подвійне «поняття» була б об'єднання купи функцій типу τ → a, τ → bі так далі. Це можна зробити, використовуючи подвійний тип суми - тип товару. Отже, з огляду на дві вищезгадані функції (викликані fта g), ми можемо створити одну подібну:

both  τ  (a, b)
both x = (f x, g x)

Тип (a, a)- це функтор прямо, так що він, безумовно, відповідає нашому поняттю F-вуглегебри. Цей особливий трюк дозволяє нам упакувати купу різних функцій - або, для методів OOP - в одну функцію типу τ → f τ.

Елементи нашого типу Cявляють собою внутрішній стан об’єкта. Якщо об’єкт має деякі читабельні властивості, вони повинні мати можливість залежати від стану. Найбільш очевидний спосіб зробити це - зробити їх функцією C. Отже, якщо ми хочемо властивість довжини (наприклад object.length), ми мали б функцію C → Int.

Ми хочемо методи, які можуть приймати аргументи та змінювати стан. Для цього нам потрібно взяти всі аргументи і викласти новий C. Давайте уявимо setPositionметод , який приймає xі yкоординату object.setPosition(1, 2). Це буде виглядати наступним чином : C → ((Int, Int) → C).

Тут важливою закономірністю є те, що "методи" та "властивості" об'єкта беруть сам об'єкт за перший аргумент. Це так само, як selfпараметр у Python і подобається неявним thisдля багатьох інших мов. Коалгебра по суті тільки инкапсулирует поведінку приймає selfпараметр: це те , що перший Cв C → F Cце.

Тож давайте все це складемо разом. Давайте уявимо клас із positionвластивістю, nameвластивістю та setPositionфункцією:

class C
  private
    x, y  : Int
    _name : String
  public
    name        : String
    position    : (Int, Int)
    setPosition : (Int, Int)  C

Для представлення цього класу нам потрібні дві частини. По-перше, нам потрібно представити внутрішній стан об’єкта; у цьому випадку вона містить лише два Ints та a String. (Це наш тип C.) Тоді нам потрібно придумати вуглегебру, що представляє клас.

data C = Obj { x, y   Int
             , _name  String }

У нас є два властивості писати. Вони досить тривіальні:

position  C  (Int, Int)
position self = (x self, y self)

name  C  String
name self = _name self

Тепер нам просто потрібно мати можливість оновити позицію:

setPosition  C  (Int, Int)  C
setPosition self (newX, newY) = self { x = newX, y = newY }

Це подібно до класу Python з його явними selfзмінними. Тепер, коли у нас є купа self →функцій, нам потрібно об'єднати їх в єдину функцію для вуглегебри. Це можна зробити за допомогою простого кортежу:

coop  C  ((Int, Int), String, (Int, Int)  C)
coop self = (position self, name self, setPosition self)

Тип ((Int, Int), String, (Int, Int) → c)-для будь c -є функтор, тому coopмає форму ми хочемо: Functor f ⇒ C → f C.

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

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

Коротше кажучи, F-вуглегебра являє собою клас, що має купу властивостей та методів, які залежать від selfпараметра, що містить внутрішній стан кожного об'єкта.

Інші категорії

Поки ми говорили про алгебри та вугелі як типи Хаскелла. Алгебра - це просто тип τз функцією, f τ → τа вуглегебра - це лише тип τз функцією τ → f τ.

Тим НЕ менше, ніщо не пов'язує ці ідеї Haskell самі по собі . Насправді вони зазвичай вводяться з точки зору наборів та математичних функцій, а не типів та функцій Haskell. Дійсно, ми можемо узагальнити ці поняття до будь-яких категорій!

Ми можемо визначити F-алгебру для деякої категорії C. Спочатку нам потрібен функтор F : C → C- це ендофунктор . (Все Haskell Functors фактично endofunctors від Hask → Hask.) Тоді алгебра тільки об'єкт Aз Cз морфізма F A → A. Вуглегебра те саме, за винятком як A → F A.

Що ми отримуємо, розглядаючи інші категорії? Що ж, ми можемо використовувати ті самі ідеї в різних контекстах. Як монади. У Haskell монада - це певний тип M ∷ ★ → ★з трьома операціями:

map         β)  (M α  M β)
return    α  M α
join      M (M α)  M α

mapФункція просто є доказом того , що Mє Functor. Тож можна сказати, що монада - це просто функтор з двома операціями: returnі join.

Функціонери самі утворюють категорію, причому морфізми між ними становлять так звані "природні перетворення". Природне перетворення - це лише спосіб перетворення одного функтора в іншого, зберігаючи його структуру. Ось приємна стаття, яка допомагає пояснити ідею. У ньому йдеться про те concat, що саме joinдля списків.

З функціонерами Haskell склад двох функторів є самим функтором. У псевдокоді ми могли б написати це:

instance (Functor f, Functor g)  Functor (f  g) where
  fmap fun x = fmap (fmap fun) x

Це допомагає нам мислити joinяк відображення f ∘ f → f. Тип joinє ∀α. f (f α) → f α. Інтуїтивно ми бачимо, як функцію, дійсну для всіх типів, αможна розглядати як перетворення f.

returnє подібною трансформацією. Його тип є ∀α. α → f α. Це виглядає інакше - перший α- не "в" функтор! До щастя, ми можемо це виправити, додавши тотожний функтор є: ∀α. Identity α → f α. Так returnє перетворення Identity → f.

Тепер ми можемо думати про монаду як просто алгебру, засновану на якомусь функторі fз операціями f ∘ f → fта Identity → f. Це не виглядає звично? Це дуже схоже на моноїд, який був просто деяким типом τз операціями τ × τ → τі () → τ.

Таким чином, монада - це як моноїд, за винятком того, що ми маємо тип. Це однакова алгебра, лише в іншій категорії. (Ось звідки, наскільки мені відомо, словосполучення "Монада - це просто моноїд у категорії ендофункторів".)

Тепер у нас є ці дві операції: f ∘ f → fі Identity → f. Щоб отримати відповідну вуглегебру, ми просто перегортаємо стрілки. Це дає нам дві нові операції: f → f ∘ fі f → Identity. Ми можемо перетворити їх у типи Haskell, додавши змінні типу, як зазначено вище, надаючи нам ∀α. f α → f (f α)і ∀α. f α → α. Це виглядає як визначення комуни:

class Functor f  Comonad f where
  coreturn  f α  α
  cojoin    f α  f (f α)

Так комонада є те коалгеброй в категорії endofunctors.


45
Це неймовірно цінно. Мені вдалося розкрити деяку інтуїцію щодо цілого бізнесу з F-алгебри з читання та прикладів (наприклад, із бачення їхнього використання з катамофризмами), але це все просто зрозуміло навіть для мене. Дякую!
Луїс Касільяс

28
Це чудове пояснення.
Edward KMETT

5
@EdwardKmett: Дякую Чи нормально речі, які я додав про класи та об’єкти? Я читаю про це лише сьогодні, але це, здається, має сенс.
Тихон Єлвіс

7
Для чого це варто: "Категорія ендофаніторів" тут - точніше категорія, об'єктами якої є ендофайнери на якійсь категорії та стрілки яких - природні перетворення. Це моноїдна категорія, з композицією функтора, що відповідає (,)і ідентичністю функтора (). Моноїдний об'єкт в межах моноїдної категорії - це об'єкт зі стрілками, відповідним вашій моноїдній алгебрі, який описує моноїдний об'єкт у Hask з типами виробів як моноїдальну структуру. Моноїдний об’єкт у категорії ендофайнерів на C - це монада на C, тому так, ваше розуміння правильне. :]
Каліфорнія Макканн

8
Це було епічним закінченням!
jdinunzio

85

F-алгебри та F-вуглегебри - це математичні структури, які допомагають міркувати про індуктивні типи (або рекурсивні типи ).

F-алгебри

Почнемо спочатку з F-алгебр. Я постараюся бути максимально простим.

Я думаю, ви знаєте, що таке рекурсивний тип. Наприклад, це тип для списку цілих чисел:

data IntList = Nil | Cons (Int, IntList)

Очевидно, що він є рекурсивним - дійсно, його визначення стосується самого себе. Його визначення складається з двох конструкторів даних, які мають такі типи:

Nil  :: () -> IntList
Cons :: (Int, IntList) -> IntList

Зауважте, що я написав тип Nilяк () -> IntList, а не просто IntList. Це фактично еквівалентні типи з теоретичної точки зору, оскільки ()тип має лише одного мешканця.

Якщо ми напишемо підписи цих функцій більш заданим теоретичним способом, то отримаємо

Nil  :: 1 -> IntList
Cons :: Int × IntList -> IntList

де 1є набір одиниць (набір з одним елементом) і A × Bоперація є поперечним добутком двох множин Aі B(тобто набір пар, (a, b)де aпроходить через всі елементи Aі bпроходить через усі елементи B).

Неперервне з'єднання двох множин Aі Bявляє собою множину, A | Bяка є об'єднанням множин {(a, 1) : a in A}і {(b, 2) : b in B}. По суті це сукупність усіх елементів як з, так Aі Bз кожним із цих елементів, "позначених" як належність до того Aчи іншого B, тож коли ми виберемо будь-який елемент, A | Bми одразу дізнаємось, чи походить цей елемент Aчи з нього B.

Ми можемо "об'єднати" Nilі Consфункції, тому вони будуть утворювати єдину функцію, що працює над набором1 | (Int × IntList) :

Nil|Cons :: 1 | (Int × IntList) -> IntList

Дійсно, якщо Nil|Consфункція застосовується до ()значення (яке, очевидно, належить 1 | (Int × IntList)заданому), то воно поводиться так, ніби воно було Nil; якщо Nil|Consвоно застосовується до будь-якого значення типу (Int, IntList)(такі значення також є у наборі1 | (Int × IntList) , воно поводиться як Cons.

Тепер розглянемо інший тип даних:

data IntTree = Leaf Int | Branch (IntTree, IntTree)

Він має такі конструктори:

Leaf   :: Int -> IntTree
Branch :: (IntTree, IntTree) -> IntTree

які також можна об'єднати в одну функцію:

Leaf|Branch :: Int | (IntTree × IntTree) -> IntTree

Видно, що обидві ці joinedфункції мають аналогічний тип: вони обидва схожі

f :: F T -> T

де Fце свого роду перетворення , яке приймає наш тип і дає більш складний тип, який складається з xі |операції, звичаї Tі , можливо , інші типи. Наприклад, для IntListіIntTree F виглядає так:

F1 T = 1 | (Int × T)
F2 T = Int | (T × T)

Ми можемо відразу помітити, що будь-який алгебраїчний тип можна записати таким чином. Дійсно, саме тому їх називають "алгебраїчними": вони складаються з ряду "сум" (об'єднань) та "продуктів" (перехресних продуктів) інших типів.

Тепер ми можемо визначити F-алгебру. F-алгебра - це просто пара (T, f), де Tє певний тип і fє функцією типу f :: F T -> T. У наших прикладах F-алгебри є (IntList, Nil|Cons)і (IntTree, Leaf|Branch). Однак зауважте, що незважаючи на той тип fфункцій, однаковий для кожного F, Tі fвони можуть бути довільними. Наприклад, (String, g :: 1 | (Int x String) -> String)або (Double, h :: Int | (Double, Double) -> Double)для деяких gіh є також F-алгебрами відповідного F.

Після цього ми можемо ввести гомоморфізми F-алгебри, а потім початкові F-алгебри , які мають дуже корисні властивості. Фактично, (IntList, Nil|Cons)це початкова F1-алгебра і (IntTree, Leaf|Branch)є початковою F2-алгеброю. Я не буду представляти точні визначення цих термінів та властивостей, оскільки вони складніші та абстрактніші, ніж потрібно.

Тим не менш, той факт, що, скажімо, (IntList, Nil|Cons)є F-алгеброю, дозволяє нам визначити foldподібну функцію для цього типу. Як відомо, fold - це певна операція, яка перетворює деякий рекурсивний тип даних в одне кінцеве значення. Наприклад, ми можемо скласти список цілих чисел у єдине значення, що є сумою всіх елементів у списку:

foldr (+) 0 [1, 2, 3, 4] -> 1 + 2 + 3 + 4 = 10

Можна узагальнити таку операцію на будь-якому рекурсивному типі даних.

Далі йде підпис foldrфункції:

foldr :: ((a -> b -> b), b) -> [a] -> b

Зауважте, що я використовував дужки, щоб відокремити перші два аргументи від останнього. Це не реальна foldrфункція, але вона ізоморфна їй (тобто ви можете легко дістати одну від іншої і навпаки). Частково застосований foldrматиме такий підпис:

foldr ((+), 0) :: [Int] -> Int

Ми можемо бачити, що це функція, яка бере список цілих чисел і повертає єдине ціле число. Давайте визначимо таку функцію з точки зору нашого IntListтипу.

sumFold :: IntList -> Int
sumFold Nil         = 0
sumFold (Cons x xs) = x + sumFold xs

Ми бачимо, що ця функція складається з двох частин: перша частина визначає поведінку цієї функції Nilчастиною IntList, а друга частина визначає поведінку функції Consчастково.

Тепер припустимо, що ми програмуємо не в Haskell, а на якійсь мові, яка дозволяє використовувати алгебраїчні типи безпосередньо в підписах типів (ну, технічно Haskell дозволяє використовувати алгебраїчні типи через кортежі та Either a bтип даних, але це призведе до зайвої багатослівності). Розглянемо функцію:

reductor :: () | (Int × Int) -> Int
reductor ()     = 0
reductor (x, s) = x + s

Видно, що reductorце функція типу F1 Int -> Int, як і у визначенні F-алгебри! Дійсно, пара (Int, reductor)- це F1-алгебра.

Оскільки IntListє початковою алгеброю F1, для кожного типу Tі для кожної функції r :: F1 T -> Tіснує функція, яка називається катаморфізмом для r, яка перетворюється IntListна T, і така функція є унікальною. Дійсно, у нашому прикладі катаморфізм для reductorє sumFold. Зауважте, як reductorі sumFoldсхожі: вони мають майже однакову структуру! У reductorвизначенні sпараметр використання (типу якого відповідає T) відповідає використанню результату обчислення sumFold xsу sumFoldвизначенні.

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

(append [4, 5, 6]) [1, 2, 3] = (foldr (:) [4, 5, 6]) [1, 2, 3] -> [1, 2, 3, 4, 5, 6]

Ось як це виглядає на нашому IntList:

appendFold :: IntList -> IntList -> IntList
appendFold ys ()          = ys
appendFold ys (Cons x xs) = x : appendFold ys xs

Ще раз спробуємо виписати редуктор:

appendReductor :: IntList -> () | (Int × IntList) -> IntList
appendReductor ys ()      = ys
appendReductor ys (x, rs) = x : rs

appendFold- це катаморфізм, для appendReductorякого трансформується IntListв IntList.

Отже, по суті, F-алгебри дозволяють нам визначити "складки" на рекурсивних структурах даних, тобто операції, які зводять наші структури до деякого значення.

F-вуглегебри

F-вуглегебри - це так званий "подвійний" термін для F-алгебр. Вони дозволяють нам визначити unfoldsдля рекурсивних типів даних, тобто спосіб побудувати рекурсивні структури з деякого значення.

Припустимо, у вас такий тип:

data IntStream = Cons (Int, IntStream)

Це нескінченний потік цілих чисел. Єдиний конструктор має такий тип:

Cons :: (Int, IntStream) -> IntStream

Або з точки зору множин

Cons :: Int × IntStream -> IntStream

Haskell дозволяє визначати відповідність на конструкторах даних, тому ви можете визначити наступні функції, що працюють над IntStreams:

head :: IntStream -> Int
head (Cons (x, xs)) = x

tail :: IntStream -> IntStream
tail (Cons (x, xs)) = xs

Ви, природно, можете "об'єднати" ці функції в одну функцію типу IntStream -> Int × IntStream:

head&tail :: IntStream -> Int × IntStream
head&tail (Cons (x, xs)) = (x, xs)

Зауважте, як результат функції збігається з алгебраїчним поданням нашого IntStreamтипу. Подібне може бути зроблено і для інших рекурсивних типів даних. Можливо, ви вже помітили закономірність. Я маю на увазі сімейство функцій типу

g :: T -> F T

де Tякийсь тип. Відтепер ми визначимось

F1 T = Int × T

Тепер F-вуглегебра - це пара (T, g), де Tє тип і gє функцією типу g :: T -> F T. Наприклад, (IntStream, head&tail)це F1-вуглегебра. Знову ж таки, як у F-алгебрах, gі Tможе бути довільним, наприклад, (String, h :: String -> Int x String)також є F1-вуглегебра протягом деякої години.

Серед усіх F-вуглегебр є так звані кінцеві F-вуглегебри , які є подвійними до початкових F-алгебр. Наприклад, IntStreamє кінцева F-вуглегебра. Це означає, що для кожного типу Tі для кожної функції p :: T -> F1 Tіснує функція, яка називається анаморфізмом , яка перетворюється Tна IntStream, і така функція є унікальною.

Розглянемо наступну функцію, яка генерує потік послідовних цілих чисел, починаючи з заданої:

nats :: Int -> IntStream
nats n = Cons (n, nats (n+1))

Тепер перевіримо функцію natsBuilder :: Int -> F1 Int, тобто natsBuilder :: Int -> Int × Int:

natsBuilder :: Int -> Int × Int
natsBuilder n = (n, n+1)

Знову ми можемо побачити деяку схожість між natsта natsBuilder. Це дуже схоже на з'єднання, яке ми спостерігали раніше з редукторами і складками. natsє анаморфізмом для natsBuilder.

Інший приклад - функція, яка приймає значення і функцію і повертає потік послідовних застосувань функції до значення:

iterate :: (Int -> Int) -> Int -> IntStream
iterate f n = Cons (n, iterate f (f n))

Його функція конструктора наступна:

iterateBuilder :: (Int -> Int) -> Int -> Int × Int
iterateBuilder f n = (n, f n)

Тоді iterateанаморфізм для iterateBuilder.

Висновок

Отже, коротко кажучи, F-алгебри дозволяють визначати складки, тобто операції, які зводять рекурсивну структуру вниз до єдиного значення, а F-вуглегебри дозволяють зробити зворотне: побудувати [потенційно] нескінченну структуру з одного значення.

Насправді в Хаскеллі F-алгебри і F-вуглегебри збігаються. Це дуже приємна властивість, яка є наслідком наявності значення "нижнього" в кожному типі. Тож у Haskell можна створити як складки, так і розгортання для кожного рекурсивного типу. Однак теоретична модель, що стоїть за цим, є більш складною, ніж та, яку я представив вище, тому я навмисно її уникав.

Сподіваюсь, це допомагає.


Тип і визначення appendReductorвиглядає дещо дивно, і насправді мені не допомогли побачити викрійку ... :) Чи можете ви двічі перевірити, чи правильно це? .. Як мають виглядати типи редукторів взагалі? У визначенні rтам, F1визначається IntList, чи це довільний F?
Макс Галкін

37

Переглядаючи навчальний посібник Навчальний посібник з (спільної) алгебри та (спільної) індукції повинен дати вам деяке уявлення про когелгеру в інформатиці.

Нижче наведено цитування, щоб переконати вас,

Загалом, програма деякою мовою програмування маніпулює даними. Під час розвитку інформатики за останні кілька десятиліть стало зрозуміло, що абстрактний опис цих даних бажаний, наприклад, для того, щоб програма не залежала від конкретного представлення даних, на яких вона працює. Також така абстрактність полегшує докази коректності.
Це бажання призвело до використання алгебраїчних методів у інформатиці, у галузі, що називається алгебраїчною специфікацією або абстрактною теорією типів даних. Об'єктом дослідження є типи даних самі по собі, використовуючи поняття про методики, знайомі з алгебри. Типи даних, які використовуються комп'ютерними вченими, часто породжуються із заданої колекції (конструкторських) операцій, і саме з цієї причини «ініціальність» алгебр відіграє таку важливу роль.
Стандартні алгебраїчні методи виявилися корисними для відображення різних істотних аспектів структур даних, що використовуються в інформатиці. Але виявилося важко алгебраїчно описати деякі властиві їм динамічні структури, що виникають при обчисленні. Такі структури зазвичай передбачають поняття держави, яке може бути перетворене різними способами. Формальні підходи до таких державних динамічних систем зазвичай використовують автомати або перехідні системи, як класичні ранні посилання.
Протягом останнього десятиліття поступово зростало уявлення про те, що такі державні системи не слід характеризувати як алгебри, а як так звані когелгебри. Це формальний дуал алгебри, таким чином, який буде точним у цьому підручнику. Подвійне властивість "ініціальності" для алгебр, а саме остаточність виявилося вирішальним для таких когелгер. І принцип логічного міркування, необхідний для таких кінцевих когелгер, - це не індукція, а коіндукція.


Прелюдія, про теорію категорій. Теорія категорій повинна бути перейменована в теорію функторів. Оскільки категорії - це те, що потрібно визначити, щоб визначити функтори. (Більше того, функтори - це те, що потрібно визначити для визначення природних перетворень.)

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

Що таке F-алгебра? Це алгебра функтора. Це просто вивчення загальної приналежності функтора.

Як це може бути посилання на інформатику? Програму можна розглядати як структурований набір інформації. Виконання програми відповідає модифікації цього структурованого набору інформації. Добре звучить, що виконання повинно зберігати структуру програми. Тоді виконання може розглядатися як додаток функтора над цим набором інформації. (Той, що визначає програму).

Чому F-co-алгебра? Програми є суттєвими подвійними, оскільки описуються інформацією та діють на неї. Тоді в основному інформацію, яка складає програму та робить їх зміненими, можна переглядати двояко.

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

Потім на цьому етапі я хотів би сказати це,

  • F-алгебра - це вивчення функціональної трансформації, що діє над Всесвітом Даних (як визначено тут).
  • F-co-алгебри - це вивчення функціональної трансформації, що діє на Всесвіт держави (як визначено тут).

Протягом життя програми співіснують дані та стан, і вони доповнюють один одного. Вони подвійні.


5

Почну з речей, які, очевидно, пов'язані з програмуванням, а потім додам деякі матеріали з математики, щоб вони були максимально конкретними та приземленими.


Процитуємо деяких вчених-комп’ютерів щодо спільної індукції ...

http://www.cs.umd.edu/~micinski/posts/2012-09-04-on-understanding-coinduction.html

Індукція - це про кінцеві дані, коіндукція - про нескінченні дані.

Типовим прикладом нескінченних даних є тип лінивого списку (потік). Наприклад, скажімо, що у нас є наступний об'єкт у пам'яті:

 let (pi : int list) = (* some function which computes the digits of
 π. *)

Комп'ютер не може вмістити всі π, оскільки він має лише обмежену кількість пам'яті! Але те, що він може зробити, це провести кінцеву програму, яка призведе до будь-якого довільно тривалого розширення π, яке ви хочете. Поки ви використовуєте лише кінцеві фрагменти списку, ви можете обчислити цей нескінченний список стільки, скільки вам потрібно.

Однак врахуйте наступну програму:

let print_third_element (k : int list) =   match k with
     | _ :: _ :: thd :: tl -> print thd


 print_third_element pi

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

http://adam.chlipala.net/cpdt/html/Coinductive.html

У ледачих функціональних мовах програмування, як Haskell, нескінченні структури даних є скрізь. Нескінченні списки та більш екзотичні типи даних забезпечують зручні абстракції для спілкування між частинами програми. Досягнення подібної зручності без нескінченних лінивих структур, у багатьох випадках, вимагає акробатичних перетворень контрольного потоку.

http://www.alexandrasilva.org/#/talks.html приклади вуглегебри Олександри Сільви


Зв’язок оточуючого математичного контексту із звичайними завданнями програмування

Що таке "алгебра"?

Алгебраїчні структури зазвичай виглядають так:

  1. Речі
  2. Які речі можуть зробити

Це має звучати як об'єкти з 1. властивостями та 2. методами. Або ще краще, це має звучати як підписи типу.

Стандартні математичні приклади включають моноїдну ⊃ групу ⊃ вектор-простір ⊃ "алгебру". Моноїди - це як автомати: послідовності дієслів (наприклад, f.g.h.h.nothing.f.g.f). gitЖурнал , який завжди додає історії і ніколи не видаляє це буде моноїд , але не група. Якщо ви додасте обертання (наприклад, від’ємні числа, дроби, коріння, видалення накопиченої історії, зняття розбитого дзеркала), ви отримаєте групу.

Групи містять речі, які можна додавати або віднімати разом. Наприклад, Durations можна додавати разом. (Але Dateне може.) Терміни живуть у векторному просторі (а не лише у групі), оскільки їх також можна масштабувати за допомогою зовнішніх чисел. (Підпис типу scaling :: (Number,Duration) → Duration.)

Алгебри ⊂ вектор-простори можуть зробити ще одне: є деякі m :: (T,T) → T. Називайте це "множенням" чи ні, тому що після того, як ви залишитесь Integers, менш очевидно, яким має бути "множення" (або "експоненція" ).

(Ось чому люди дивляться на (теоретичні за категоріями) універсальні властивості: сказати їм, що мусить робити чи бути таким, як :

універсальна властивість товару )


Алгебри → вугілля

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

Лічильник зазвичай - це слід (сума діагональних записів), хоча знову ж таки важливо, що робить ваш граф ; trace- просто хороша відповідь для матриць.

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


Приборкання (не) структурованих даних

Математики можуть моделювати щось цікаве, як TQFT , тоді як програмістам доводиться боротися

  • дати / часи ( + :: (Date,Duration) → Date),
  • місця ( Paris(+48.8567,+2.3508)! Це форма, а не крапка.),
  • неструктурований JSON, який повинен певним чином бути послідовним,
  • XML неправильно, але закрити,
  • неймовірно складні дані ГІС, які повинні задовольнити навантаження розумних відносин,
  • регулярні вирази, які щось для вас означали , але означають значно менше загинути.
  • CRM, який повинен містити всі номери телефонів та місцезнаходження вілли, керівництво (іменини) дружини та дітей, день народження та всі попередні подарунки, кожен з яких повинен задовольняти "очевидні" відносини (очевидно для клієнта), які неймовірно важко кодувати,
  • .....

Комп'ютерні науковці, коли говорять про вуглегебри, зазвичай мають на увазі операції, що ставлять перед собою, як декартовий продукт. Я вважаю, що саме це мають на увазі люди, коли говорять на кшталт "Алгебри - це вугілля у Хаскеллі". Але настільки, що програмістам доводиться моделювати складні типи даних, такі як Place, Date/Timeі - Customerі роблять ці моделі максимально схожими на реальний (або принаймні погляд кінцевого користувача на реальний світ) - я вважаю дуалів, може бути корисним поза межами світу наборів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.