Что означает «коалгебра» в контексте программирования?

339

Я слышал термин «коалгебры» несколько раз в функциональном программировании и кругах PLT, особенно когда речь идет об объектах, комонадах, линзах и тому подобном. Погуглив этот термин, вы найдёте страницы, которые дают математическое описание этих структур, что для меня довольно непостижимо. Может ли кто-нибудь объяснить, что означают коалгебры в контексте программирования, каково их значение и как они связаны с объектами и комонадами?

missingfaktor
источник
21
Могу ли я порекомендовать отличную книгу Джереми Гиббонса «Образцы в FP»: templatesinfp.wordpress.com и его вполне понятную статью «Расчет функциональных программ»? Они оба охватывают угольные алгебры довольно строго (по сравнению, например, с сообщением в блоге), но они также довольно самодостаточны для тех, кто немного знает Хаскель.
Кристофер Мичински
2
@KristopherMicinski, очень интересно. Спасибо!
фактор

Ответы:

474

Алгебры

Я думаю, что для начала нужно понять идею алгебры . Это просто обобщение алгебраических структур, таких как группы, кольца, моноиды и так далее. В большинстве случаев эти вещи представлены в терминах наборов, но поскольку мы среди друзей, я расскажу о типах Haskell. (Я не могу удержаться от использования некоторых греческих букв - они заставляют все выглядеть круче!)

Таким образом, алгебра - это просто тип τс некоторыми функциями и тождествами. Эти функции принимают различное количество аргументов типа τи выдают τ: uncurried, все они выглядят так (τ, τ,…, τ) → τ. Они также могут иметь «идентичности» - элементы, τкоторые имеют специальное поведение с некоторыми функциями.

Простейшим примером этого является моноид. Моноид - это любой тип τс функцией 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-коалгебры. Этот конкретный трюк позволяет нам упаковать множество различных функций - или, для ООП, методов - в одну функцию типа τ → 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. Действительно, мы можем обобщить эти понятия на любые категории!

Мы можем определить F-алгебру для некоторой категории C. Во-первых, нам нужен функтор, F : C → Cто есть эндофунктор . (Все Haskell Functors фактически endofunctors от Hask → Hask.) Тогда алгебра только объект Aиз Cс морфизма F A → A. Коалгебра такая же, как с A → F A.

Что мы получаем, рассматривая другие категории? Ну, мы можем использовать одни и те же идеи в разных контекстах. Как монады. В Хаскеле монада - это некий тип 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 α)

Таким образом, комонада является коалгеброй в категории эндофункторов.

Тихон Джелвис
источник
45
Это невероятно ценно. Мне удалось распутать некоторые интуиции об этом бизнесе F-алгебры из чтения и примеров (например, увидев их использование с катаморфизмом), но это все совершенно ясно, даже для меня. Спасибо!
Луис Касильяс
28
Это отличное объяснение.
Эдвард КМЕТТ
5
@EdwardKmett: Спасибо. Хорошо, что я добавил о классах и объектах? Я только прочитал об этом сегодня, но это, кажется, имеет смысл.
Тихон Джелвис
7
Для чего это стоит: «категория эндофункторов» - это, точнее, категория, чьи объекты являются эндофункторами в некоторой категории, а стрелки - естественными преобразованиями. Это моноидальная категория, с композицией функторов, соответствующей (,)функтору идентичности (). Моноидный объект в моноидальной категории - это объект со стрелками, соответствующими вашей моноидной алгебре, который описывает моноидный объект в Hask с типами продуктов в качестве моноидальной структуры. Моноидный объект в категории эндофункторов на C - это монада на C, так что да, ваше понимание верно. :]
CA McCann
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подобную функцию для этого типа. Как вы знаете, сложение - это своего рода операция, которая преобразует некоторый рекурсивный тип данных в одно конечное значение. Например, мы можем сложить список целых чисел в одно значение, которое является суммой всех элементов в списке:

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, и такая функция является уникальной. Действительно, в нашем примере катаморфизм для reductoris 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-коалгеброй для некоторого h.

Среди всех 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-коалгебры совпадают. Это очень приятное свойство, которое является следствием наличия значения «bottom» в каждом типе. Таким образом, в Haskell могут быть созданы как сгибы, так и сгибы для каждого рекурсивного типа. Однако теоретическая модель, стоящая за этим, является более сложной, чем та, которую я представил выше, поэтому я намеренно избегал ее.

Надеюсь это поможет.

Владимир Матвеев
источник
Тип и определение appendReductorвыглядит немного странно и не очень помогли мне увидеть шаблон там ... :) Можете ли вы еще раз проверить, что он правильный? .. Как должны выглядеть типы редукторов в целом? В определении rтам F1определяется IntList или это произвольный F?
Макс Галкин
37

Просмотр учебного пособия Учебник по (со) алгебрам и (со) индукции должен дать вам некоторое представление о коалгебре в информатике.

Ниже приводится цитата, чтобы убедить вас,

В общем, программа на каком-то языке программирования манипулирует данными. Во время развития информатики за последние несколько десятилетий стало ясно, что желательно абстрактное описание этих данных, например, чтобы гарантировать, что программа не зависит от конкретного представления данных, с которыми она работает. Также такая абстрактность облегчает доказательства корректности.
Это желание привело к использованию алгебраических методов в информатике, в области, называемой алгебраической спецификацией или абстрактной теорией типов данных. Объектом исследования являются сами типы данных, использующие понятия методов, знакомых по алгебре. Типы данных, используемые учеными-компьютерщиками, часто генерируются из данной совокупности (конструкторских) операций, и именно по этой причине «начальность» алгебр играет такую ​​важную роль.
Стандартные алгебраические методы оказались полезными для захвата различных важных аспектов структур данных, используемых в информатике. Но оказалось трудно алгебраически описать некоторые из изначально динамических структур, возникающих в вычислительной технике. Такие структуры обычно включают понятие состояния, которое может быть преобразовано различными способами. Формальные подходы к таким основанным на состоянии динамическим системам обычно используют автоматы или системы переходов, как классические ранние ссылки.
В течение последнего десятилетия постепенно росло понимание того, что такие основанные на состоянии системы следует описывать не как алгебры, а как так называемые коалгебры. Это формальные двойственные алгебры, которые будут уточнены в этом уроке. Двойственное свойство «начальности» для алгебр, а именно конечность, оказалось решающим для таких коалгебр. И принцип логического мышления, необходимый для таких конечных коалгебр, - это не индукция, а коиндукция.


Прелюдия, о теории категорий. Теория категорий должна быть переименована в теорию функторов. Поскольку категории - это то, что нужно определить, чтобы определить функторы. (Более того, функторы - это то, что нужно определить, чтобы определить естественные преобразования.)

Что за функтор? Это преобразование из одного набора в другой, сохраняющее их структуру. (Для более подробной информации есть много хорошего описания в сети).

Что такое F-алгебра? Это алгебра функтора. Это всего лишь изучение универсальной уместности функтора.

Как это может быть ссылка на информатику? Программу можно рассматривать как структурированный набор информации. Выполнение программы соответствует модификации этого структурированного набора информации. Звучит хорошо, что выполнение должно сохранить структуру программы. Тогда выполнение можно рассматривать как приложение функтора над этим набором информации. (Тот, который определяет программу).

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

  • Данные, которые можно определить как информацию, обрабатываемую программой.
  • Состояние, которое можно определить как информацию, передаваемую программой.

Тогда на этом этапе я бы хотел сказать,

  • F-алгебра - это изучение функториального преобразования, действующего над Вселенной Данных (как здесь определено).
  • F-коалгебры - это исследование функторных преобразований, действующих на вселенную государства (как здесь определено).

В течение жизни программы данные и состояние сосуществуют, и они дополняют друг друга. Они двойственны.

zurgl
источник
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

Эта программа должна напечатать третью цифру числа пи. Но в некоторых языках любой аргумент функции оценивается перед передачей в функцию (строгая, а не ленивая оценка). Если мы используем этот порядок сокращения, то наша вышеупомянутая программа будет всегда вычислять цифры числа Пи, прежде чем она может быть передана нашей функции принтера (что никогда не происходит). Так как у машины нет бесконечной памяти, программе в конечном итоге не хватит памяти и произойдет сбой. Это может быть не лучшим порядком оценки.

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 могут быть добавлены вместе. (Но Dates не может.) Длительности живут в векторном пространстве (не только в группе), потому что они также могут быть масштабированы по внешним числам. (Тип подписи scaling :: (Number,Duration) → Duration.)

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

(Вот почему люди обращаются к категории (теоретико-) универсальным свойствам: сказать им , что умножение должно делать или быть , как :

универсальное свойство продукта )


Алгебры → коалгебры

Комультиплицирование легче определить таким образом, который кажется не произвольным, чем умножение, потому что для перехода от T → (T,T)него можно просто повторить один и тот же элемент. («Диагональное отображение» - как диагональные матрицы / операторы в спектральной теории)

Счет - это обычно след (сумма диагональных входов), хотя, опять же, важно то, что делает ваш счет ; traceэто просто хороший ответ для матриц.

В общем, причина взглянуть на двойственное пространство состоит в том, что в этом пространстве легче мыслить. Например, иногда проще думать о нормальном векторе, чем о плоскости, к которой он нормален, но вы можете управлять плоскостями (включая гиперплоскости) с помощью векторов (и теперь я говорю о знакомом геометрическом векторе, как в трассировщике лучей) ,


Укрощение (не) структурированных данных

Математики могут моделировать что-то забавное, как TQFT , в то время как программисты должны бороться с

  • даты / время ( + :: (Date,Duration) → Date),
  • места ( Paris(+48.8567,+2.3508)! Это форма, а не точка.),
  • неструктурированный JSON, который должен быть согласованным в некотором смысле,
  • неправильный, но близкий XML,
  • невероятно сложные данные ГИС, которые должны удовлетворять множеству разумных отношений,
  • регулярные выражения, которые что- то значат для вас, но значат гораздо меньше для perl.
  • CRM, который должен содержать все номера телефонов руководителя и местонахождение виллы, имена его (теперь бывшей) жены и детей, день рождения и все предыдущие подарки, каждый из которых должен удовлетворять «очевидным» отношениям (очевидным для клиента), которые невероятно трудно кодировать,
  • .....

Информатики, говоря о коалгебрах, обычно имеют в виду операции с сеткой, как декартово произведение. Я считаю, что именно это имеют в виду люди, когда говорят: «Алгебры - это коалгебры в Хаскеле» Но в той степени, в которой программистам приходится моделировать сложные типы данных, такие как Place, Date/Timeи Customer- и делать эти модели максимально похожими на реальный мир (или, по крайней мере, на взгляд конечного пользователя на реальный мир) - я считаю, может быть полезным за пределами только сет-мира.

isomorphismes
источник