Что общего у нормального распределения, конечных автоматов, хеш-таблиц, произвольных предикатов, строк, выпуклых оболочек, афинных преобразований, файлов конфигураций и стилей CSS? А что объединяет целые числа, типы в Haskell, произвольные графы, альтернативные функторы, матрицы, регулярные выражения и статистические выборки? Наконец, можно ли как-то связать между собой булеву алгебру, электрические цепи, прямоугольные таблицы, теплоизоляцию труб или зданий и изображения на плоскости? На эти вопросы есть два важных ответа: 1) со всеми этими объектами работают программисты, 2) эти объекты имеют сходную алгебраическую структуру: первые являются моноидами, вторые — полукольцами, третьи — алгебрами де Моргана.
Если мы, программисты, научимся сами и научим компьютер работать с моноидами, полукольцами и прочими структурами, то всем нам работать станет легче. Потому что на алгебраические структуры накладываются определённые ограничения, а из ограничений следуют и определённые гарантии. Все эти ограничения и гарантии справедливы для всех представителей структуры, а это значит, что можно строить универсальный инструментарий для работы с моноидами, полукольцами, алгебрами и, следовательно, со всеми перечисленными выше и многими другими объектами. Посмотрите, сколько математиками определено алгебраических структур! И ведь для них есть теоремы, какие-то для программистов полезные, какие-то пока не очень, но все они надёжны, как самые лучшие библиотеки программ!
Всё становится ещё вкуснее, когда внутри структуры строятся гомоморфизмы — преобразования, сохраняющие структуру. Гомоморфизмы дают возможность "переключаться" с одного объекта структуры на другой — со списков на числа, с графов на матрицы, с регулярных выражений на графы, с электрических цепей на булевы выражения или на графическое изображения этих цепей! Ну а уж изоморфизмы — это вообще песня! Если программист считает что математика ему не нужна и что все эти морфизмы-шморфизмы это просто абстрактная чушь, то он лишает себя не только прекрасного, надёжного инструмента, но, самое главное, существенной части удовольствия от своей работы.
В этой статье мы покажем на примере алгебр де Моргана, как строить алгебраические абстракции, как определять для них гомоморфизмы и свободные алгебры, и как это можно использовать, конечно.
Статья рассчитана на тех, кто уже знает что такое классы типов, функторы, моноиды и, в целом, хорошо представляет себе что такое программирование на Haskell. С другой стороны, она может быть интересна всем, кто интересуется как в программировании строятся абстракции, и как их можно применить к реальным задачам.
Немного про моноиды
Моноиды — это абстракция композиции. В свою очередь, композиция — ключевое понятие в функциональном программировании. Именно поэтому моноиды встречаются здесь так часто и играют настолько важные роли. Про ценность и богатство применения моноидов говорилось и писалось много. Здесь мы перечислим основные их свойства, которые нам пригодятся в дальнейшем изложении.
Определение
Моноидом называется очень простая структура: это множество, на котором определена ассоциативная бинарная операция, имеющая единственный нейтральный элемент. Коммутативность операции не требуется, однако нейтральный элемент должен быть нейтральным, будучи как первым, так и вторым операндом.
В языке Haskell для моноидов определён класс Monoid
:
class Monoid a where
mempty :: a
mappend :: a -> a -> a
а библиотека Data.Monoid
предоставляет ряд полезных экземпляров этого класса, таких как Sum
, Product
, First
, Last
, Endo
и т.п.
Ключевыми свойствами моноида являются ассоциативность и единственность нейтрального элемента. Эти ограничения позволяют обобщать моноидальные операции для произвольного порядка выполнения (в том числе, и для параллельного).
Дуальный моноид
Для любого моноида можно определить дуальный ему моноид, в котором бинарная операция принимает аргументы в обратном порядке:
> Dual [1,2,3] <> Dual [5,6]
Dual { getDual = [5,6,1,2,3] }
Связь со свёрткой
Хорошо известно, насколько полезным и универсальным инструментом является свёртка: с одной стороны, через свёртку выражается дюжина полезных универсальных функций, обрабатывающих коллекции, а с другой, сворачивать можно списки, деревья и прочие индуктивные коллекции. Свёрток определено достаточно много. Сворачивать коллекцию можно как справа, так и слева, это может повлиять и на результат (в зависимости от ассоциативности сворачивающей функции), и на эффективность вычислений. Сворачивать можно вводя начальный результат свёртки, на случай пустой коллекции, или обходясь без него, если есть гарантии, что коллекция не пуста. Поэтому в стандартной библиотеке Haskell и библиотеке Data.Foldable
так много различных свёрток:
foldr, foldl, foldr1, foldl1, foldl', foldl1'
Если же речь идёт о свёртке в моноид, достаточно одной функции, в силу ассоциативности моноидальной операции и гарантии наличия нейтрального элемента.
fold :: (Monoid m, Foldable t) => t m -> m
Чаще используется вариант, позволяющий явно указывать какой именно моноид следует использовать для свёртки:
foldMap :: (Monoid m, Foldable t) => (a -> m) -> t a -> m
Если коллекция является функтором, то определение функции foldMap
можно дать такое:
foldMap f = fold . fmap f
Свободный моноид
Иногда существует возможность "переходить" между моноидами, напомним, такие преобразования называются гомоморфизмами. При этом существует такой моноид, из которого можно построить гомоморфизм в любой другой. Такой моноид называется свободным, его роль в Haskell играют списки.
Свободные структуры отражают свойства алгебраической системы, но ничего "не вычисляют", не изменяют данные и не теряют информации. В известном смысле, они представляют собой формальный язык для описания элемента алгебраической системы или категории, при этом гомоморфизмы можно рассматривать в качестве интерпретаторов этого языка. Такая аналогия нам пригодится в дальнейшем.
Подробнее о моноидах и их применении в Haskell можно почитать в статьях:
- Monoids Tour
- Monoids: Theme and Variations (Functional Pearl)
- Monoids and Finger Trees
- Finger Trees: A Simple General-purpose Data Structure
Алгебры де Моргана
Для многих объектов существует несколько способов композиции и одним моноидом тут не обойтись. Если таких способов два, то говорят о кольцах, полукольцах, решётках и различных алгебрах. Рассмотрим несколько примеров, наводящих на мысль об общности алгебраической структуры, лежащей в их основе.
Булева алгебра
Что мы знаем о булевой алгебре:
- Определена на множестве значений
{True, False}
и описывается с помощью трёх операций: конъюнкции, дизъюнкции и отрицания. - Конъюнкция и дизъюнкция формируют моноиды.
- Элемент
False
является нейтральным для дизъюнкции и поглощающим для конъюнкции. - Элемент
True
, в свою очередь, нейтральный элемент для конъюнкции и поглощающий для дизъюнкции. - Отрицание является инволюцией, то есть, эта операция обратна самой себе.
- Выполняется закон де Моргана.
Алгебра электрических сопротивлений
Давайте рассмотрим задачу расчёта произвольной двухполюсной электрической цепи, которая может состоять из сопротивлений и допускает последовательное или параллельное соединение элементов цепи.
- Для них определены две операции последовательного () и параллельного () соединения.
- Соединения формируют моноиды (они ассоциативны и имеют нейтральные элементы).
- Разрыв цепи является нейтральным для параллельного и поглощающим для последовательного соединений.
- Короткое замыкание является нейтральным для последовательного и поглощающим для параллельного соединений.
- Сопротивлениям соответствует обратная величина — проводимость и переход от сопротивлений к проводимости является инволюцией.
- Эффективное сопротивление для последовательного и параллельного соединений вычисляется по формулам:
Эти выражения можно переписать симметричным способом:
в котором узнаются законы де Моргана!
Алгебра таблиц
Прямоугольные таблицы также можно комбинировать двумя основными способами: объединяя строки () или столбцы ():
1 2 3 a b 1 2 3 a b 4 5 6 <-> c d = 4 5 6 c d 7 8 9 e f 7 8 9 e f 1 2 3 a b c 1 2 3 4 5 6 <|> d e f = 4 5 6 7 8 9 7 8 9 a b c d e f
Кроме того, для таблиц можно определить инволюцию — транспонирование, и нетрудно проверить, что оба способа комбинирования таблиц образуют моноиды с одинаковым нейтральным элементом — пустой таблицей. И, наконец, один способ комбинирования можно получить из другого: например, объединить две таблицы по вертикали можно сначала транспонировав обе таблицы, затем объединив их по горизонтали и транспонировав результат.
Используя свойства инволюции, опять получаем законы де Моргана:
Определение алгебры де Моргана
Пора дать формальное определение для структуры, с которой мы работаем:
Алгеброй де Моргана называется структура, состоящая из множества, на котором определены две бинарные операции и , условно, называющиеся сложением и умножением, а также операция инволюции , для которых выполняются следующие условия:
- операция образует моноид с нейтральным элементом :
операция образует моноид с нейтральным элементом :
выполняется мультипликативное свойство нуля:
свойство инволюции:
дуальность нуля и единицы относительно инволюции:
законы де Моргана:
Строго говоря, умножение ещё должно быть дистрибутивно по отношению к сложению, но в некоторых случаях (например для электрических цепей) это может не выполняться. Зато если выполняется, то можно сложное выражение, состоящее из комбинации сложений и умножений привести к форме суммы одночленов, раскрыв все скобки, или, наоборот, упростить выражение, вынося за скобки общие множители.
Иногда инволюция выполняет роль инверсии, то есть, порождает обратный элемент для операции умножения. Тогда выполняется следующее тождество:
Это выполняется для булевой алгебры и алгебры сопротивлений, но неверно для таблиц.
Абстракция алгебры де Моргана на Haskell
Перед началом работы подключим несколько расширений:
{-# LANGUAGE DeriveFunctor, FlexibleInstances, GeneralizedNewtypeDeriving #-}
Определим для алгебр де Моргана класс типов:
class DeMorgan a where {-# MINIMAL inv,((<->)|(<|>)),(zero|one) #-} inv :: a -> a zero :: a zero = inv one one :: a one = inv zero (<->) :: a -> a -> a a <-> b = inv (inv a <|> inv b) (<|>) :: a -> a -> a a <|> b = inv (inv a <-> inv b)
В этом определении мы уже используем законы де Моргана для того, чтобы упростить работу с классом. В описании экземпляра достаточно определить только одну из двух моноидальных операций, её нейтральный элемент и инволюцию.
Создадим первый экземпляр класса
DeMorgan
для логических данных:
instance DeMorgan Bool where zero = False inv = not (<->) = (&&)
Из соображений эффективности, конечно, стоит явно определить и оператор
<|>
, но мы оставим это лаконичное определение, чтобы убедиться в том, что оно в полной мере задаёт алгебру для типаBool
:
> True <|> False True > one :: Bool True
Булева алгебра, это хорошо, но уж больно просто. Давайте её обобщим, построив алгебру для нечёткой логики в духе Лотфи Заде. Для этого определим тип-обёртку
Fuzzy
и потребуем у компилятора вывести для него числовые свойства:
newtype Fuzzy = Fuzzy { deFuzzify :: Double } deriving (Show, Num, Fractional, Ord, Eq) instance DeMorgan Fuzzy where zero = 0 inv x = fuzzify $ 1 - x a <|> b = fuzzify $ max a b fuzzify x = 0 `max` x `min` 1
> deFuzzify $ one 1 > deFuzzify $ 0.2 <-> 0.4 0.2 > deFuzzify $ 0.8 <|> 0.4 <-> 0.5 0.5 > deFuzzify $ 0.3 <|> 0.4 <-> 0.5 0.4
Определим простейшую алгебру де Моргана для таблиц, представленных вложенными списками:
instance DeMorgan [[a]] where zero = [] (<|>) = (++) inv = transpose
здесь мы использовали функцию
transpose
из библиотекиData.List
. Вот как работает эта алгебра:
> inv [[1,2],[3,4]] [[1,3],[2,4]] > [[1,2],[3,4]] <-> [[5],[6]] [[1,2,5],[3,4,6]] > [[1,2],[3,4]] <|> [[5,6]] [[1,2],[3,4],[5,6]]
Давайте применим её для написания простенькой табулирующей функции
table
table :: (Show a, Show b) => (a -> a -> b) -> [a] -> [[String]] table f vals = ([[" "]] <-> h) <|> (inv h <-> c) where h = [show <$> vals] c = [show <$> [ f x y | x <- vals] | y <- vals] showTable tbl = putStrLn $ unlines $ unwords <$> tbl
> showTable $ table mod [1..6] 1 2 3 4 5 6 1 0 0 0 0 0 0 2 1 0 1 0 1 0 3 1 2 0 1 2 0 4 1 2 3 0 1 2 5 1 2 3 4 0 1 6 1 2 3 4 5 0
Наконец, вспомним о сопротивлениях и построим алгебру де Моргана для чисел, точнее, для числовых типов, для которых определена операция деления. Для этого определим тип-обёртку
Frac
:
newtype Frac a = Frac {getFrac :: a} deriving (Show, Num, Fractional, Functor) instance Fractional a => DeMorgan (Frac a) where zero = 0 inv = (1/) (<->) = (+)
> getFrac $ 2 <-> 6 8.0 > getFrac $ 2 <|> 6 1.5 > getFrac $ 1 <-> (2 <|> (1 <-> 1)) 2.0
В качестве примера, выразим эффективное сопротивление цепи, показанной на рисунке и вычислим силу тока при напряжении 12 V:
> getFrac $ ((6 <|> 12) <-> 4) <|> (3 <-> 5) 4.0 > getFrac $ 12 / ((6 <|> 12) <-> 4) <|> (3 <-> 5) 3.0
Дуальная алгебра
Для расчёта эффективной жёсткости сложной системы пружин, или ёмкости батареи конденсаторов, тоже годится тип
Frac
, но для этих расчётов нужно поменять местами операторы<|>
и<->
, а также элементыzero
иone
. Математики с радостью узнают в этом преобразовании переход к дуальной алгебре. Звучит красиво, но не хотелось бы писать2 <-> 3
имея в виду параллельное соединение пружин. Было бы хорошо сохранить семантику операторов, но изменить способ вычислений.
Тут математика снова даёт нам возможность воспользоваться её плодами. Как и для любого моноида, для любой алгебры де Моргана определена дуальная ей алгебра, в которой "всё наоборот". Опишем это обстоятельство на языке Haskell. Создадим новый тип-обёртку
Dual
для дуальных алгебр и сообщим, что если алгебраa
является алгеброй де Моргана, то и дуальная ей тоже будет алгеброй де Моргана.
newtype Dual a = Dual {getDual :: a} deriving (Show, Num, Fractional, Eq, Ord, Functor) instance DeMorgan a => DeMorgan (Dual a) where zero = Dual one one = Dual zero inv x = inv <$> x Dual a <|> Dual b = Dual $ a <-> b Dual a <-> Dual b = Dual $ a <|> b
Теперь можно рассчитать эффективную жёсткость системы из трёх пружин, одна из которых последовательно соединяется с парой других, соединёных параллельно:
> getFrac . getDual $ 600 <-> (200 <|> 300) 450.0
Вывод типов позволяет указывать какую именно алгебру мы используем только "на выходе" из вычислений. Так мы можем явно и, в то же время, просто, изменять способ вычисления выражения.
Свободная алгебра
А что если мы захотим представлять и рассчитывать цепи, содержащие не только сопротивления, но и ключи, конденсаторы, катушки и т.д.? И не только рассчитывать, но и изображать их или сохранять в файл? Для этого нам понадобится свободная алгебра.
Цепь будем представлять некоторой структурой данных, ничего не вычисляющей, но отражающей алгебраическую структуру цепи и образующей алгебру де Моргана:
data Circuit a = Zero | One | Elem a | Inv (Circuit a) | Par (Circuit a) (Circuit a) | Seq (Circuit a) (Circuit a) deriving (Show, Functor)</hs></pre> instance DeMorgan (Circuit a) where zero = Zero one = One (<->) = Seq (<|>) = Par inv = Inv
Инволюция или инверсия для элемента цепи не имеет определённого смысла, но для корректности и общности мы включили её в описание типа.
Самый первый и естественный гомоморфизм — это преобразование типа
Circuit
в произвольный тип, для которого определена алгебра де Моргана:
reduce :: DeMorgan a => Circuit a -> a reduce circ = case circ of Zero -> zero One -> one Elem b -> b Inv a -> inv (reduce a) Par a b -> reduce a <|> reduce b Seq a b -> reduce a <-> reduce b
Функция
reduce
подобна функцииfold
, которая сворачивает список моноидов в произвольный моноид. Таким же образом, как от функцииfold
для функторов можно произвести функциюfoldMap
, можно образовать функциюreduceMap
:
reduceMap :: DeMorgan b => (a -> b) -> Circuit a -> b reduceMap = reduce . fmap f
Эта функция позволяет явно указать какую именно алгебру де Моргана следует использовать при интерпретации цепи.
Согласитесь, обнаружение такой красивой симметрии в определениях функций свёртки для моноидов и нашей алгебры доставляет эстетическое удовольствие!
Различные задачи расчёта цепей
Для построения цепей организуем предметно-ориентированный тип
Lumped
, который позволит нам кроме значений параметров элементов цепиValue a
, явно представить короткое замыканиеShort
и разрыв цепиBreak
:
data Lumped a = Short | Value a | Break deriving (Show, Functor) instance DeMorgan s => DeMorgan (Lumped s) where zero = Break Value r1 <-> Value r2 = Value $ r1 <-> r2 Short <-> r = r r <-> Short = r _ <-> Break = Break Break <-> _ = Break inv Short = Break inv Break = Short inv (Value r) = Value (inv r)
Введём тип для элементов: сопротивлений, ёмкостей и индуктивностей, а также конструкторы элементов цепи:
data Element = R Double | C Double | L Double deriving Show res = Elem . R cap = Elem . C coil = Elem . L key True = Zero key False = One
Наконец, напишем простенькую цепь для опытов.
s :: Bool -> Circuit Element s k = res 10 <-> ((res 2 <-> coil 5e-3 <-> key k) <|> cap 10e-9)
Теперь мы готовы строить различные интерпретации нашей свободной алгебры.
Функция
connect
определяет является ли цепь замкнутой, используя булеву алгебру:
connected :: Circuit Element -> Bool connected = reduceMap f where f el = case el of R _ -> True C _ -> False L _ -> True
> connected (s True) True > connected (s False) False
Функция
resistance
определяет эффективное сопротивление цепи для постоянного тока, используя алгебру для дробных чисел:
resistance :: Circuit Element -> Lumped Double resistance = fmap getFrac . reduceMap (fmap Frac . f) where f el = case el of R r -> Value r C _ -> Break L _ -> Short
Здесь вычислительная работа производится внутри типа
Lumped
, Который мы объявили функтором.
> resistance (s True) Value 12 > resistance (s False) Break
Для расчёта импеданса цепи потребуетcя подключить модуль
Data.Complex
:
impedance :: Double -> Circuit Element -> Lumped (Complex Double) impedance w = fmap getFrac . reduceMap (fmap Frac . f) where f el = Value $ case el of R r -> r :+ 0 C c -> 1 / (0 :+ w*c) L l -> 0 :+ w*l
> impedance 1e3 (s False) Value (10.0 :+ (-99999.99999999997)) > impedance 1e3 (s True) Value (12.00020001420084 :+ 5.0002100065000405) > impedance 1e6 (s False) Value (10.0 :+ (-100.0)) > impedance 1e6 (s True) Value (10.000832986116954 :+ (-102.04081598653629))
Если речь идёт о расчёте батареи конденсаторов, то нужно поменять алгебру на дуальную:
capacity :: Circuit Element -> Lumped Double capacity = fmap (getFrac . getDual) . reduceMap (fmap (Dual . Frac) . f) where f el = case el of R _ -> Short C c -> Value c L _ -> Short
Обратите внимание на то, как во всех этих интерпретациях мы указывали способ расчёта характеристик цепи, определяя лишь способ расчёта для отдельных элементов и выбирая подходящую алгебру для свёртки.
А если нам нужно будет изобразить цепь, то можно воспользоваться великолепной библиотекой Diagrams для построения векторной графики. Она предоставляет два комбинатора для относительного расположения изображений:
(|||)
— рядом по горизонтали и(===)
— рядом по вертикали. Это значит, что для изображений можно построить алгебру, подобную алгебре таблиц, а потом, описав как изображаются отдельные элементы цепи, построить гомоморфизм из свободной алгебры в алгебру изображений и нарисовать произвольную схему. Кстати, если вы хотите увидеть филигранное и нетривиальное использование моноидов, обратите внимание на эту библиотеку и на статьи, с ней связанные.
Заключение
Мы не рассмотрели всех возможностей, которые даёт использование абстракции для алгебры де Моргана. Можно было привести пример расчёта теплоизоляции здания с окнами и сложным покрытием стен; или моделирование неньютоновских жидкостей цепями из элементов жёсткости и вязкости.
Для моноидов и алгебры де Моргана выполняется ряд простых теорем, например, известно, что произведение типов-моноидов тоже образует моноид. Эвивалентное утверждение верно для алгебр де Моргана, оно даёт возможность за один проход по структуре вычислять сразу несколько характеристик. Более того, легко можно построить эпиморфизм для алгебры в моноид и вычислять не только алгебраические характеристики, но и любые моноидальные. Как самый простой пример: одновременно с расчётом сопротивлений можно вычислить суммарную мощность выделяемую цепью, которая будет описываться простым суммирующим моноидом.
Свободная алгебра даёт ещё одну возможность, существенную, например, для задач нечёткой логики. Ещё не вычисленное выражение можно упростить, если воспользоваться дистрибутивностью алгебры. Эту процедуру можно выполнить во время выполнения программы, то есть с произвольными данными, но перед интенсивными вычислениями. На что способны свободные структуры показывает Wolfram Mathematica, в которой все выражения, по существу, представляют собой свободные алгебры.
По сравнению с лошадью или с автомобилем, поезд имеет существенное ограничение — он может двигаться только по железной дороге. И у железной дороги есть существенное ограничение — по ней могут двигаться только поезда. Но эти ограничения дают новые возможности — можно существенно снизить трение, увеличить скорость и построить сложную и эффективную автоматическую систему управления движением сотен составов. На автомагистрали нельзя ездить на мопеде или на тракторе. Это ограничение. Но если мы гарантируем его, то можем рассчитывать на высокие скорости и высокую пропускную способность трассы.
Функциональное программирование добавляет в повседневную практику программиста различные ограничения, и законы. Изменять состояние нельзя, композиция должна быть ассоциативной, аппликативные функторы и монады должны удовлетворять законам аппликативных функторов и монад и т.д. Это может раздражать, но вместе с этими ограничениями и законами приходят гарантии, что код будет работать именно так, а не иначе. А самое главное, именно неукоснительное следование этим законам, в принципе, и позволяет говорить в программе о функторах, монадах, ассоциативности и дистрибутивности, алгебрах и прочих категориях, а значит и пользоваться наработками многих поколений математиков и применять их в повседневной работе. Так что все эти сложные штуки — не костыли ФП, как может показаться, а ставшие доступными благодаря развитию аппаратной части компьютеров, новые возможности. Не стоит ими пренебрегать.
- операция образует моноид с нейтральным элементом :
shuhray
Если назвать моноид «полугруппой с единицей», становится не так страшно. Дописал учебник теории категорий
https://github.com/George66/Textbook
samsergey
А разве так страшно? :) Термин моноид мне нравится больше чем полугруппа с единицей. Второй термин сложный (в лингвистическом смысле) — он состоит из двух понятий, каждое из которых требует определения. Более того, полугруппа — тоже сложный термин, наводящий на мысль о том, что же такое группа, а она, увы, определяется вовсе не так просто и несколько уводит в сторону от понятия моноид.
Алгебраическое понимание моноида очень хорошо ложится на принцип декомпозиции и композиции в программировании. Более того, освоив это понятие, становится проще понять смысл категории, как набора объектов и морфизмов образующих моноид (агебраический) по отношению к композиции. И моноид в теории категории определяется естественно, и вполне понятно почему он там так называется.
Наконец, пресловутая монада, при ясном понимании, что такое моноид становится, наконец, просто специальным способом композиции.
Хорошее слово, ёмкое. И означающее что-то одно и фундаментальное. В «полугруппе с единицей» есть что-то неполноценное, и группа-то наполовинку, и единицы могло бы не быть… :)
Поздравляю с окончанием работы над учебником!
shuhray
Вот именно потому, что похоже на слово «монада». Полугруппа с единицей — вещь достаточно простая, к монаде сама по себе имеет мало отношения (а к группе много). Группы надо бы знать в любом случае (на популярном уровне книжки с картинками Александрова). Слово «моноид», мне кажется, лучше использовать для некоторого более сложного обобщения (как в книжке Маклейна).
samsergey
Соглашусь с вами в том, что понимание понятия группы и связанных с ними понятий симметрия и инвариант, не менее важно, чем понимание композиции. Что же, это очень хорошо, что к моноидам можно подойти как со стороны групп, как и со стороны теории категории. И у нас есть выбор своего удобного пути.
Ares_ekb
Очень клевый у вас учебник, буду везде рекламировать :) Хотя, чувствую, что я так и не пойму адъюнкции и лемму Йонеды. Все эти формулы, диаграммы я конечно понимаю. Но мне не хватает каких-то аналогий из программирования, более конкретных примеров. Например, в своей статье я описывал универсальное свойство, (ко)пределы через поиск минимального или максимального множества в категории множеств. Это очень частный пример, но зато он понятен и от него можно переходить к чему-то более сложному. На мой взгляд, для программистов такой вычислительный взгляд на теорию категорий единственный возможный. Программисты и математики мыслят совершенно по-разному. Я смутно понимаю, что адъюнкции — это частный случай универсального свойства. Универсальное свойство, которые выполняется для всех объектов в категории. Но пока на конкретных множествах в этом не убедишься, всё это пустой звук.
В вашей книге есть глава про исчисление высказываний. С точки зрения программиста это можно использовать для описания семантики языков программирования. Интересно, вам попадались такие примеры? Чтобы для какого-то простенького языка программирования семантика была описана полностью с помощью теории категорий? По денотационной семантике таких учебников полно, а по категориальной ещё ни разу не встречал.
shuhray
Я сам научился теории категорий, читая несколько лет бестолковую книжку Голдблатта и научные статьи в ГПНТБ. По мере научения пробовал на людях (на маленьком семинаре в МГУ). Ситуация такая: теорию категорий у нас немножко знают алгебраисты, но им нужен довольно специальный кусок для их специальных целей. Что такое «топос», например, они не знали. Я решил примеры брать в основном из самой теории категорий (множества, меняющиеся со временем и т.п.), поскольку красивые картинки, все конструкции наглядны, а специальных программистских примеров я всё равно много не подберу, поскольку не программист. Есть много англоязычных учебников теории категорий специально для программистов — надеюсь, теперь их легче будет переводить.
beavis88
Бестолковая??? Да это лучшая книга по теоркату которые мне приходилось видеть.
Я бы ваш подход назвал бестолковым и даже вредным, так как изложение нестрогое, без расстановки правильных акцентов на исходных и производных понятиях. В математике нет королвеского пути, нельзя научиться по книжкам для чайников, программистов, инженеров и т.п.
shuhray
Однако, встречал только одного человека (себя), который сумел научиться по книжке Голдблатта. Вы второй! На русском языке по теоркату есть алгебраические книжки про гомотопии, есть книга Джонстона, по которой учиться нельзя (она для профессионалов) и Голдблатт, в этом смысле он был лучший.
shuhray
А по поводу программирования с помощью категорий приходит на ум «категорная абстрактная машина» (наберите на libgen фамилию Curien)
Ares_ekb
Я тоже учился сам, прочитал/пролистал много книг и статей, из которых мало чего понял. Действительно что-то соображать начал только после:
D.E. Rydeheard, R.M. Burstall. Computational Category Theory
H.J. Schneider. Graph Transformations. An Introduction to the Categorical Approach
Curien не читал и про libgen не слышал, спасибо! Есть языки программирования, основанные полностью на теории категорий. Но хочется что-то типа D. Schmidt. Denotational Semantics: A Methodology for Language Development. Только чтобы вместо множеств, доменов и монотонных функций использовалась теория категорий. Чтобы описывался какой-нибудь простой язык программирования буквально с несколькими видами выражений и операторов (объявление переменных, циклы, условия, арифметика). И чтобы описывалось как с помощью теории категорий описать семантику этого языка. Все статьи, какие видел по этой теме, либо слишком сложные, либо в них описываются частности. Ну, видимо, тема пока слишком специфическая, буду пока использовать денотационную семантику.
gearbox
жОсткий вы человек ) Есть же https://www.gitbook.com/ для таких целей.