it-swarm.com.ru

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

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

329
missingfaktor

Алгебры

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

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

Простейшим примером этого является моноид. Моноид - это любой тип τ с функцией mappend ∷ (τ, τ) → τ и идентификатором mzero ∷ τ. Другие примеры включают в себя такие вещи, как группы (которые похожи на моноиды, за исключением дополнительной функции invert ∷ τ → τ), кольца, решетки и так далее.

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

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

F-алгебры

Тем не менее, мы не совсем закончили с факторингом. Пока что у нас есть несколько функций τⁿ → τ. Мы действительно можем сделать хитрый трюк, чтобы объединить их все в одну функцию. В частности, давайте посмотрим на моноиды: у нас есть mappend ∷ (τ, τ) → τ и mempty ∷ () → τ. Мы можем превратить их в одну функцию, используя тип суммы —Either. Это будет выглядеть так:

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

На самом деле мы можем использовать это преобразование многократно для объединения all функций τⁿ → τ в одну, для any алгебры. (Фактически, мы можем сделать это для любого количества функций a → τ, b → τ и т.д. Для anya, b,….)

Это позволяет нам говорить об алгебрах как о типе τ с функцией single из некоторого беспорядка Eithers в один τ. Для моноидов это беспорядок: Either (τ, τ) (); для групп (у которых есть дополнительная операция τ → τ) это: Either (Either (τ, τ) τ) (). Это разные типы для каждой другой структуры. Так что же общего у всех этих типов? Наиболее очевидным является то, что все они являются просто суммами продуктов - алгебраическими типами данных. Например, для моноидов мы могли бы создать тип аргумента моноида, который работает для any monoid τ:

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 представляют состояние internal объекта. Если объект имеет некоторые читаемые свойства, они должны иметь возможность зависеть от состояния. Самый очевидный способ сделать это - сделать их функцией 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 и 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)— для anyc— является функтором, поэтому coop имеет желаемую форму: Functor f ⇒ C → f C.

Учитывая это, C вместе с coop образуют коалгебру, которая определяет класс, который я дал выше. Вы можете увидеть, как мы можем использовать эту же технику, чтобы указать любое количество методов и свойств для наших объектов.

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

Короче говоря, F-коалгебра представляет класс, имея набор свойств и методов, которые все зависят от параметра self, содержащего внутреннее состояние каждого объекта.

Другие категории

До сих пор мы говорили об алгебрах и коалгебрах как о типах Хаскеля. Алгебра - это просто тип τ с функцией f τ → τ, а коалгебра - это просто тип τ с функцией τ → f τ.

Однако на самом деле ничто не связывает эти идеи с Haskell per se. Фактически они обычно вводятся в терминах множеств и математических функций, а не типов и функций Haskell. Действительно, мы можем обобщить эти понятия на любые категории!

Мы можем определить F-алгебру для некоторой категории C. Во-первых, нам нужен функтор F : C → C— то есть endofunctor. (Все Functors в Haskell на самом деле являются эндофункторами из 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 α. Интуитивно мы видим, как функцию, допустимую для all типов α, можно рассматривать как преобразование 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 α)

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

465
Tikhon Jelvis

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 и ​​IntTreeF выглядит следующим образом:

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 существует функция с именем catamorphism для 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-coalgebra - это пара (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 существует функция с именем anamorphism, которая преобразует 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 могут быть созданы как сгибы, так и сгибы для каждого рекурсивного типа. Однако теоретическая модель, стоящая за этим, является более сложной, чем та, которую я представил выше, поэтому я намеренно избегал ее.

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

82
Vladimir Matveev

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

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

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


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

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

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

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

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

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

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

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

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

37
zurgl

Я начну с материалов, которые явно связаны с программированием, а затем добавлю некоторые математические материалы, чтобы они были настолько конкретными и практичными, насколько я могу.


Давайте процитируем некоторых компьютерных ученых по коиндукции ...

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 examples of coalgebras by Alexandra Silva


Соотнесение окружающего математического контекста с обычными задачами программирования

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

Алгебраические структуры обычно выглядят так:

  1. Stuff
  2. Что может сделать материал

Это должно звучать как объекты с 1. свойствами и 2. методами. Или даже лучше, это должно звучать как подписи типа.

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

Группы содержат вещи, которые можно сложить или вычесть вместе. Например, Durations могут быть добавлены вместе. (Но Dates не может.) Длительности живут в векторном пространстве (не только в группе), потому что они также могут масштабироваться по внешним числам. (Подпись типа scaling :: (Number,Duration) → Duration.)

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

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

universal property of product )


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

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

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

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


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

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

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

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

4
isomorphismes