thedeemon: (Default)
[personal profile] thedeemon
А мы продолжаем передачу "теоркат для самых маленьких", в которой я рассказываю сам себе постигнутые азы. Сегодня мы увидим, как из нескольких категорных определений внезапно следуют привычные законы арифметики и как частный случай - арифметика типов. Теория категорий - это такая абстракция над теориями: мы берем какие-то теории, видим в них некоторые сущности и связи между ними, абстрагируемся от содержания (внутренней структуры) этих сущностей, концентрируясь лишь на связях между ними, и так получаем категорию (сущности становятся непрозрачными объектами, связи - стрелками между ними). Довольно разные теории при таком абстрагировании могут нам дать очень похожие категории, и доказав нечто один раз на уровне категорий, мы автоматически получаем множество теорем - по теореме для каждой из вписывающихся в эту категорию теорий. Так чуть ниже парой строк на CPL мы докажем, что
lcm(a, gcd(b,c)) == gcd(lcm(a,b), lcm(a,c))   (a,b,c - натуральные числа)
(a, Either b c) == Either (a,b) (a,c)         (a,b,c - типы данных в Хаскеле)
A & (B | C) == (A & B) | (А & C)              (A,B,C - логические высказывания)

и еще примерно 9213 теорем похожего вида.

Вот возьмем логику высказываний и логическую связку ИЛИ (|) оттуда, дизъюнкцию. Ее определением служат правила ввода и избавления. Правила ее ввода: если доказана достоверность высказывания А, то доказана и достоверность А|В. Если доказано В, то также доказано А|В. Правило избавления от дизъюнкции: если доказаны импликации А=>C и В=>С и доказано А|В, то отсюда выводится достоверность С.

Очень похоже выглядит сумма двух типов в ЯП (sum type aka discriminated union). В том же хаскеле это Either a b. Если есть значение х типа а, то можно построить значение Left х типа Either a b. Аналогично из значения х типа b можно построить значение Right x того же типа Either a b. Что можно с такими значениями делать, кроме как где-то хранить и куда-то передавать? Можно только одно - использовать в выражении case (и сводящемся к нему паттерн-матчинге). Конструкция case берет по сути две функции: одна что-то умеет делать со значением типа "а", другая - со значением типа "b", обе они возвращают результат одного типа "с", этот тип становится типом всего выражения case. Например,
h :: Either Int String -> Float
h x = case x of
  Left i -> fromIntegral i + 2.0
  Right s -> read s + 3.0

Сходство суммы типов и связки ИЛИ кристаллизуется в категорном определении суммы: объект sum(А,В) называется суммой объектов А и В, если из А и из В в него есть стрелки, и для любого другого объекта С, в который тоже есть стрелки из А и В, должна существовать уникальная стрелка из sum(A,B) в C да такая, чтобы получающаяся диаграмма коммутировала:

"Диаграмма коммутирует" означает, что если на ней между двумя точками (объектами) есть несколько различных путей, то неважно каким путем двигаться из первой точки во вторую, результат будет одинаковый. Применим ли мы сразу fromIntegral i + 2.0 к целому числу или сперва обернем в Left, а затем разберем через case и ту же функцию - результат один. Воспользуемся ли сразу импликацией А => С для вывода С из доказанного А или сперва выведем достоверность А|В, а затем с помощью той же импликации и правила избавления от | выведем С, результат не изменится. На языке CPL определение суммы записыватся так:
left object sum(a,b) with case is
  left  : a -> sum
  right : b -> sum
end object

Мы требуем наличия стрелок left и right из А и В в объект-сумму и даем имя case универсальному морфизму, идущему из объекта-суммы в любой похожий объект (т.е. в который есть тоже стрелки из А и В). Из такого определения с учетом универсальности интерпретатор CPL сам выводит такие соотношения:
f0: *b -> *a  f1: *c -> *a
-----------------------------
case(f0,f1): sum(*b,*c) -> *a
- equations:
(LEQ1): case(f0,f1).left=f0
(LEQ2): case(f0,f1).right=f1
(LFEQ): sum(f0,f1)=case(left.f0,right.f1)
(LCEQ): g.left=f0 & g.right=f1 => g=case(f0,f1)

(точка означает композицию стрелок)

Из одного этого определения можно получить важное следствие. Если у нас есть объект sum(A,B), то что можно сказать о sum(B,A)? Это какой-то другой объект, но в него тоже есть стрелки из А и В, а значит должна быть стрелка из sum(A,B) в sum(B,A), этого требует определение. Назовем ее f1. Аналогично, должна быть и стрелка из sum(B,A) в sum(A,B), назовем ее f2. Тогда их композиция f2 . f1 есть стрелка из sum(A,B) в sum(A,B). В категории у каждого объекта есть стрелка id в самого себя, а поскольку обсуждаемые стрелки тут таковы, что получаемые диаграммы с ними должны коммутировать, то не важно, попадем мы из sum(A,B) в себя по стрелке id или по стрелке f2 . f1, т.е. f2.f1 = id. Обратная композиция, f1.f2 аналогично идет из sum(B,A) в sum(B,A) и по тем же соображениям f1.f2 = id. Т.е. f1 и f2 - изоморфизмы, они делают объекты sum(A,B) и sum(A,B) изоморфными. Для теории категорий такие объекты - близнецы-братья, т.к. все связи одного объекта автоматически есть и у другого, ибо определение категории требует наличие стрелки-композиции для каждой пары последовательных стрелок. Мы только что показали коммутативность сложения: sum(A,B) == sum(В,А), где == означает изоморфность. В некоторых теориях изоморфность означает равенство, например, в категории, где объекты - натуральные числа, а стрелка означает делимость одного числа на другое, категорной суммой объектов служит gcd (наибольший общий делитель), и доказанное означает gcd(a,b) = gcd(b,a). А в категории, где объекты - типы данных, а стрелки - функции, изоморфность означает не равенство типов, а эквивалентность в том, какую информацию значения этих типов содержат. Изоморфность означает наличие стрелок-изоморфизмов, композиция которых равна id, а значит между такими типами должны существовать функции конверсии такие, что их композиция не теряет информации, сконвертив значение туда-обратно мы получим его же без изменений.

На CPL все доказательство будет выглядеть так:
let f1 = case(right, left);
Мы выше определили, что right и left - это стрелки типов *b -> sum(*a,*b) и *a -> sum(*a,*b) (звездочка перед именем означает, что это свободная переменная, произвольный объект, подобно 'a в окамле, где это произвольный тип). Тип case CPL вывел сам (приведен выше), отсюда он выводит тип f1: sum(*a,*b) -> sum(*b,*a). Соответственно, f1 . f1 будет иметь тип sum(*a,*b) -> sum(*a,*b), а значит f1.f1 = id (диаграмма должна коммутировать), и f1 - изоморфизм, доказывающий изоморфность этих двух объектов. "case(right, left)" - три слова на CPL, доказывающие коммутативность суммы сразу в куче теорий. Любое выражение на CPL определяет морфизм (стрелку), и при переводе на хаскель это выражение превращается в функцию:
f1 :: Either a b -> Either b a
f1 x = case x of
  Left a -> Right a
  Right b -> Left b


В некоторых категориях бывает определен инициальный объект. Он характеризуется тем, что из него есть по уникальной стрелке во все объекты категории. В логике высказываний таким объектом выступает особое высказывание ⊥ - ложь или абсурд. Его нельзя построить конструктивно (нет правила ввода), зато из него есть импликации во все возможные высказывания (из абсурда следует что угодно). В теории типов это пустой тип ⊥, у которого нет значений. На CPL инициальный объект (обозначим его 0) определяется так:
left object 0 with any
end object;

У него нет особенных стрелок кроме универсального морфизма any в каждый из объектов категории. CPL выводит:
any : 0 -> *a
Пользуясь этими определениями, можно составить на CPL выражение case(I,any). "I" в CPL обозначает тождественный морфизм, имеющийся у каждого объекта: I : *a -> *a. Выведенный тип для case(I,any) таков: sum(*a, 0) -> *a. Т.е. для любого объекта А есть стрелка из А+0 в А. А по определению суммы, из А есть стрелка left в А+0. И поскольку эти стрелки таковы, что соответствующие диаграммы обязаны коммутировать, наличие стрелок из А в А+0 и обратно опять нам дает изоморфизм, поэтому А+0 == А (я для простоты буду писать А+В вместо sum(A,B)). Значит, инициальный объект 0 - нейтральный элемент для сложения. В логике это превращается в знакомое правило P | False == P. В типах - Either a ⊥ == a (ведь значения типа слева всегда будут вида Left х, ибо в Right нам подставить нечего).

Сумму объектов в теоркате часто называют гуаноэкстермина копроизведением, потому что это dual к произведению: одно получается из другого разворотом всех стрелок. Соответственно, произведением объектов А и В называется такой объект prod(A,B), из которого есть стрелки в А и в В (назовем их fst и snd), и для любого похожего объекта С (из которого тоже есть стрелки в А и В) есть уникальная стрелка pair из С в prod(A,B):

Опять диаграмма должна коммутировать, т.е. fst.pair(f,g) = f и т.д. На CPL определение выглядит так:
right object prod(a,b) with pair is
  fst: prod -> a
  snd: prod -> b
end object;

Из него интерпретатор сам выводит
f0: *a -> *b  f1: *a -> *c
------------------------------
pair(f0,f1): *a -> prod(*b,*c)
- equations:
(REQ1): fst.pair(f0,f1)=f0
(REQ2): snd.pair(f0,f1)=f1
(RFEQ): prod(f0,f1)=pair(f0.fst,f1.snd)
(RCEQ): fst.g=f0 & snd.g=f1 => g=pair(f0,f1)

Ровно те же рассуждения что и с суммой позволяют показать коммутативность произведения. На CPL выражение pair(snd, fst) имеет тип prod(*a,*b) -> prod(*b,*a) и служит изоморфизмом между А*В и В*А, доказывающим их изоморфность. В логике произведением служит связка И, в категории типов - тупл, в категории натуральных чисел и делимости - lcm (наименьшее общее кратное).

Двойственным объектом к инициальному (0) является терминальный (1), характеризующийся наличием уникальных стрелок из всех объектов категории в него. На CPL его определение выглядит так:
right object 1 with !
end object;

В логике высказываний это особое высказывание "истина". В ЯП это тип unit, он же (), с единственным значением (). Его построить можно из чего угодно, функция "f x = ()" служит стрелкой из любого типа в (). Именно она названа на CPL !, где ее тип: *a -> 1. Как несложно догадаться, терминальный объект для произведения подобен инициальному для копроизведения - это нейтральный элемент, т.е. А*1 == А. Выражение "pair(I, !)" имеет тип *a -> prod(*a,1), а "fst" дает обратную стрелку из prod(*a,1) в *a. Композиции их дают стрелки из *a в *a и из prod(*a,1) в prod(*a,1), т.е. это изоморфизмы, и действительно А*1 == А. В категории с числами и делимостью 1 - это само число 1, на него делятся все натуральные числа. Получаем знакомые соотношения
(a, ()) == a
P & True == Р
lcm(a, 1) = a

Дальше нам понадобится экспоненциал. В логике это импликация (A => B), в ЯП это функциональный тип (a -> b), в категории натуральных чисел и делимости это хитро вычисляемое число. На CPL его определение:
right object exp(a,b) with curry is
  eval: prod(exp,a) -> b
end object;

C такими выводимыми интерпретатором следствиями:
f0: prod(*a,*b) -> *c
---------------------------
curry(f0): *a -> exp(*b,*c)
- equations:
(REQ1): eval.prod(curry(f0),I)=f0
(RFEQ): exp(f0,f1)=curry(f1.eval.prod(I,f0))
(RCEQ): eval.prod(g,I)=f0 => g=curry(f0)


В категории с экспоненциалами можно доказать дистрибутивность умножения относительно сложения. Вот такое выражение на CPL:
let d1 = pair( case(fst,fst), case( left.snd, right.snd ) )

определяет стрелку типа sum(prod(*a,*b),prod(*a,*c)) -> prod(*a,sum(*b,*c)), т.е. из A*B+A*C в A*(B+C). А вот так можно определить обратную:
let flip = pair(snd, fst);
let cs = case(curry(left.flip), curry(right.flip));
let d2 = eval.pair(cs.snd, fst);

d2 имеет тип prod(*a,sum(*b,*c)) -> sum(prod(*a,*b),prod(*a,*c)), т.е. это стрелка из A*(B+C) в A*B+A*C. Вместе d1 и d2 служат изоморфизмами, доказывая A*(B+C) == A*B+A*C, дистрибутивность. Они выглядят понятнее, если их перевести на хаскель (не идиоматично, зато близко к коду на CPL):
d1 :: Either (a,b) (a,c) -> (a, Either b c)
d1 x = (a,b) where
  a = case x of { Left p -> fst p; Right p -> fst p }
  b = case x of { Left p -> (Left . snd) p; Right p -> (Right . snd) p }

d2 :: (a, Either b c) -> Either (a,b) (a,c)
d2 x = ((cs . snd) x) (fst x) where
  cs s = case s of
    Left b -> \a -> Left (a,b)
    Right c -> \a -> Right (a,c)

Конечно, d2 можно записать намного проще:
d2' :: (a, Either b c) -> Either (a,b) (a,c)
d2' (a, e) = case e of
              Left b -> Left (a, b)
              Right c -> Right (a, c)

Но загвоздка в том, что в категорном и CPL-ном определении case он параметризуется стрелками (функциями): одна стрелка здесь должна уметь из значения "b" сделать Left (a, b), а вторая из "с" сделать Right (a, c), но где им для этого взять "а"? По сути, эти функции в d2' - замыкания, они берут значение "а" из окружения. Приходится их реализовать более явно: не имея значения "а", мы возвращаем не Left (a, b), а функцию, которая из "a" сделает Left (a, b). Аналогично с Right (a, c). Тип у этих двух возвращаемых функций одинаковый, и он становится типом всего выражения case. И возвращенная им функция уже применяется к параметру типа "а", взятому из исходной пары. Именно поэтому нам потребовались экспоненциалы.
CPL-ные определения стрелок d1 и d2 можно превратить в диаграммы со стрелочками (они там, особенно для d2, довольно развесистые получаются, могу потом картинку показать) и сформулировать категорное доказательство дистрибутивности. Оно намного длиннее, чем те пара строчек на CPL. Итак, дистрибутивность показали, получили упомянутые в начале поста следствия для конкретных теорий.

Раз уж у нас появился экспоненциал, докажем кое-какие его свойства. Напомню, что exp(A,B) означает для ЯП тип функции А -> В, для логики импликацию А => В, но экспоненциалом называется в силу схожести с В^A - "В в степени А". От возведения в степень разумно ожидать, что А^1 = A и 1^A=1 для любого А, т.е. exp(1,A) == А и exp(A,1) == 1. Построим такие выражения-стрелки на CPL:
let e1 = eval.pair(I,!);
let e2 = curry(I);
let e3 = curry(!);

e1 имеет тип exp(1,*a) -> *a, т.е. дает нам стрелку из А^1 в A. e2 имеет тип *a -> exp(*b, prod(*a,*b)), для любых объектов А и В е2 дает стрелку из А в exp(B, A*B), в частности для В=1 это стрелка из А в (A*1)^1, а про А*1 мы уже знаем, что он изоморфен А. Это вроде как доказывает(?) A^1==A.
Выражение е3 имеет тип *a -> exp(*b,1) и для произвольных объектов A и B дает стрелку из A в 1^B, в частности из 1 в 1^B. Ну а стрелка из 1^B в 1 всегда есть по определению 1 - терминального объекта. И опять в силу характера этих стрелок, наличие двух стрелок туда-обратно между объектами делает стрелки изоморфизмами, а объекты изоморфными. Значит, 1^A == 1 для любого А. Как следствие a -> () == () для типов и (P => True) == P для логических высказываний.

Итого, из категорных определений произведения, копроизведения, экспоненциала, инициального и терминального объектов мы вывели как следствия такие арифметические свойства:
a+b == b+a
a*b == b*a
a+0 == 0+a == a
a*1 == 1*a == a
a*(b+c) == a*b + a*c
a^1 == a
1^a == 1
И из этого набора можно получить другие следствия. Например, обозначив 1+1 за 2, получим
a*2 == a*1 + a*1 == a + a
т.е. умножение (на натуральное число) выражается через сложение не только в привычной арифметике натуральных чисел, но и в арифметике типов, например.

Кроме того можно показать, что a^2 == a*a, т.е. возведение в натуральную степень выражается через произведение. Необходимые изоморфизмы на хаскеле выглядят так:
type Two = Either () ()

p1 :: (a,a) -> (Two -> a)
p1 (a,b) = \i -> case i of { Left _ -> a; Right _ -> b }

p2 :: (Two -> a) -> (a,a)
p2 f = (f (Left ()), f (Right ()))

Их можно записать на CPL и получить категорные диаграммы, но мне сейчас лень, чуть позже сделаю. Вот такая вот арифметика. Да простят меня за капитанство, но желание поделиться и показать возможности CPL перевесило.

Date: 2012-04-20 08:17 pm (UTC)
From: [identity profile] gds.livejournal.com
> в которой я рассказываю сам себе постигнутые азы

ничего подобного про "сам себе", лично я внимательно читаю всё. И хочу ещё. И хочу, чтобы эти "азы" были в интернетах в такой понятной форме, чтобы было куда отправлять людишек.

А лингвистически, про копроизведение -- я давно догадывался! :]

Date: 2012-04-20 08:49 pm (UTC)
From: [identity profile] 109.livejournal.com
копро-изведение?

Date: 2012-04-20 09:05 pm (UTC)
From: [identity profile] gds.livejournal.com
ваистену!

Date: 2012-04-20 08:29 pm (UTC)
From: [identity profile] maxim.livejournal.com
Да будет раскрыт педагогический потенциал CPL!

Date: 2012-04-20 08:42 pm (UTC)
From: [identity profile] 109.livejournal.com
что-то я торможу. в сишарпе, тип (), он же void - это терминальный или инициальный объект?

Date: 2012-04-20 08:44 pm (UTC)
From: [identity profile] gds.livejournal.com
если брать "морфизм = функция, объект = значение", то терминальный, его из любого объекта можно получить, применив "fun _ -> ()".

Date: 2012-04-20 08:48 pm (UTC)
From: [identity profile] 109.livejournal.com
okay, a что в сишарпе инициальный объект?

Date: 2012-04-20 09:11 pm (UTC)
From: [identity profile] gds.livejournal.com
а вот не знаю про C#. Обычно, если определять категорию как в моём каменте выше, это какой-то ненаселённый тип. Например, в окамле так:
# type uninh = [ ];
type uninh = [  ]
# value f (x : uninh) = match x with [ ];
value f : uninh -> 'a = <fun>
# 

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

Date: 2012-07-02 08:17 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
если вы отображаете что-то в одно значение одного типа, то это не инициальный объект; это как раз терминальный объект.

Date: 2012-07-02 01:02 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Он наоборот из одного типа uninh "получает" значение любого другого.

Date: 2012-07-02 01:51 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
Не вижу противоречия.

Любой set переводим в set одного элемента. set одного элемента можем перевести в любой другой set множеством способов.


Стрелка из инициального объекта получает не одно любое значение из любого другого объекта, а "все сразу". Это визуализировать довольно тяжело, но это так:

Допустим, z_A: 0->A, z_B: 0->B. Поскольку из 0 в A и B существует единственная стрелка, а категория содержит стрелки и все их композиции, z_B = f . z_A для любых f: A->B (в противном случае f . z_A должна задавать в категории ещё одну стрелку 0->B).


unless именно это и имелось в виду под "из значения ненаселённого типа можно единственным образом ... получить любое значение любого типа"

Date: 2012-07-02 02:26 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Тут тоже все сразу. Просто воспользоваться такой стрелкой нельзя, т.к. неоткуда взять значение типа uninh (0).

Date: 2012-04-21 04:34 am (UTC)
From: [identity profile] mstone.livejournal.com
Похоже (http://james-iry.blogspot.com/2009/08/getting-to-bottom-of-nothing-at-all.html), инициального объекта в C# нету. А ближе всего к тому месту, где он гипотетически мог бы быть (если ограничиться ref-типами), находится null.

Date: 2012-04-20 08:57 pm (UTC)
From: [identity profile] voidex.livejournal.com
Согласен с предыдущими ораторами. Огромное спасибо за статью.
Edited Date: 2012-04-20 08:59 pm (UTC)

Date: 2012-04-23 02:38 pm (UTC)
From: [identity profile] udpn.livejournal.com
Лучше бы ты образовал себе месяцок свободного времени да написал учебник по теоркату c примерами на CPL. Он был бы куда популярнее всяких "категорий для программистов".

Date: 2012-04-23 03:14 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Мне бы сперва самому чему-то научиться, на это не один месяцок уйдет. А CPL все же недостаточно выразительный, как упоминалось ранее, на нем даже pullback не описать.

Date: 2012-04-23 05:59 pm (UTC)
From: [identity profile] udpn.livejournal.com
Тогда вопрос по теме.

Если из наличия экспоненциалов следует наличие дистрибутивности сложения относительно умножения, то из чего следует дистрибутивность умножения относительно сложения?

Date: 2012-04-23 06:51 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
В посте говорится про "дистрибутивность умножения относительно сложения" как раз. А обратное бывает где-то?

0_o

Date: 2012-04-23 06:59 pm (UTC)
From: [identity profile] udpn.livejournal.com
a || (b && c) == (a || b) && (a || c)
a && (b || c) == (a && b) || (a && c)

Re: 0_o

Date: 2012-04-24 04:22 am (UTC)
From: [identity profile] thedeemon.livejournal.com
А, ну да, торможу. Ну так
Welcome to Djinn version 2011-07-23.
Type :h to get help.
Djinn> ? e1 :: Either a (b,c) -> (Either a b, Either a c)
e1 :: Either a (b, c) -> (Either a b, Either a c)
e1 a =
     case a of
     Left b -> (Left b, Left b)
     Right (c, d) -> (Right c, Right d)
Djinn> ? e2 :: (Either a b, Either a c) -> Either a (b,c)
e2 :: (Either a b, Either a c) -> Either a (b, c)
e2 (a, b) =
     case a of
     Left c -> Left c
     Right d -> case b of
                Left e -> Left e
                Right f -> Right (d, f)

Осталось перевести на CPL. Сходу проблем и особых требований не видно.

Re: 0_o

Date: 2012-04-24 04:35 am (UTC)
From: [identity profile] thedeemon.livejournal.com
let e1 = case(pair(left,left), pair(right.fst, right.snd))

e1 : sum(*a,prod(*b,*c)) -> prod(sum(*a,*b),sum(*a,*c))

Re: 0_o

Date: 2012-04-24 06:33 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Если в e2 убрать неявные замыкания
e2 :: (Either a b, Either a c) -> Either a (b, c)
e2 (a_b, a_c') = g a_c' where
  g = case a_b of
      Left a -> \a_c -> Left a
      Right b -> \a_c -> f a_c b where        
        f a_c = case a_c of
                Left a -> \b -> Left a
                Right c -> \b -> Right (b, c)

то перевод на CPL получается такой:
let f1 = curry(left.fst);
let f2 = curry(right.flip);
let f3 = curry(eval.pair(case(f1,f2).snd, fst));
let e2 = eval.pair(case(f1,f3).fst, snd);

e2 :  prod(sum(*a,*b),sum(*a,*c)) -> sum(*a,prod(*b,*c))

Т.е. того же набора из суммы, произведения и экспоненциала хватает для построения стрелок туда-обратно.

Только вот пища для размышлений: хаскельная e2 теряет информацию...

Re: 0_o

Date: 2012-04-24 07:23 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Получается
e2 . e1 = id
но
e1 . e2 != id
поэтому изоморфизма не получилось (quickcheck подтверждает). Но на CPL
cpl> show e2.e1
e2.e1
    : sum(*a,prod(*b,*c)) -> sum(*a,prod(*b,*c))
cpl> show e1.e2
e1.e2
    : prod(sum(*a,*b),sum(*a,*c)) -> prod(sum(*a,*b),sum(*a,*c))

все стрелки построились. Где-то у меня дыра в рассуждениях про достаточность стрелок туда-сюда для изоморфности, похоже...

Re: 0_o

Date: 2012-04-25 03:58 pm (UTC)
From: [identity profile] udpn.livejournal.com
>> Только вот пища для размышлений: хаскельная e2 теряет информацию...
Ну так это же типы-суммы и типы-произведения, а не типы-конъюнкции и типы-дизъюнкции.

>> поэтому изоморфизма не получилось
Вот поэтому и не получилось. Нельзя заменять || и && на Either и (,). А вот СPL не имеет под определениями sum и prod лямбда-исчисления, поэтому для него всё нормально.

Re: 0_o

Date: 2012-04-26 03:13 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Это не для него нормально, это я наличие стрелок неправильно интерпретировал. См. свежий пост-поправку.

Date: 2012-07-02 07:49 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
а можно ещё раз - покажите разницу между A|B и B|A. (до того, как вводить изоморфизм между ними)

(Если её нет, то дело не в изоморфизме, а это просто одно и то же)

Я этой разницы не могу обнаружить. Мне кажется, разница в языке программирования возникает по другой причине - мы пытаемся в CCC построить "аналогичные конструкции" на экспоненциалах, т.е. CAxB=CBxA, но изоморфно (CA)B и изоморфно (CB)A, и вот здесь уже порядок следования A и B становится заметен.

Date: 2012-07-02 08:53 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
Важно отличить полиморфную функцию fst в отдельно взятом языке программирования от собственно стрелок между объектами.


Существует две стрелки fst=id, snd=1, раскладывающие A на проекции A и 1 со всеми необходимыми следствиями о композиции - значит, A is a product Ax1. Аналогично мы можем выбрать fst=1, snd=id и получить вывод о 1xA.

Стало быть, Ax1=1xA=A, т.е. не изоморфно, а именно A неотличимо от Ax1.

Date: 2012-07-16 10:44 am (UTC)
From: [identity profile] thedeemon.livejournal.com
Если взять объект В, изоморфный А, из него же тоже такие стрелки можно провести. Он тоже будет являться произведением Ax1, n'est-ce pas?

Date: 2012-07-16 12:32 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
да, верно, согласен.

Date: 2012-07-02 12:57 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Хороший вопрос. В теоркате очень многие (или все?) объекты определяются up to (canonical) isomorphism, поэтому порой сложно сказать, где у нас разные но изоморфные объекты, а где один объект.

Date: 2012-07-16 10:37 am (UTC)
From: [identity profile] thedeemon.livejournal.com
В учебниках по теоркату коммутативность произведения - доказываемый факт. Копроизведения, видимо, тоже.

Date: 2012-07-16 12:15 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
Да это был коммент о том, являются ли A|B и B|A разными объектами. Что определяет порядок A и B в записи A|B?

Когда мы знаем, что задаёт порядок, тогда мы можем сказать, вот A|B, а это - B|A, у которого порядок иной, но мы можем продемонстрировать изоморфизм, и т.д.

Date: 2012-07-16 01:05 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Априори неизвестно, разные это объекты или нет. Мы видим двуместную операцию, если угодно функцию от двух аргументов. Не зная ее подробностей, нет никаких оснований полагать, что порядок аргументов можно менять.

Date: 2012-07-16 02:01 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
Вот.

Я и говорю, что когда мы выражаем факт AxB=BxA в виде изоморфизма функций двух переменных, то мы имеем дело с изоморфизмом экспоненциалов, а там порядок "аргументов функции" выплывает из композиции. Мне кажется, примерно в этом и кроется тонкое различие между собственно стрелками в категории и функциями в языке.

Например, (a,b) на самом деле является "функцией" конструктора (PairB)A. Здесь построение (a,b) и (b,a) используют eval в разном порядке и потому мы можем говорить о "первом" и "втором" элементе пары. Поэтому сдаётся мне, строго говоря, в языках программирования собственно объект AxB не выразим; но мы можем получить объекты, которые ведут себя точно так же, потому что в CCC есть стрелка из всех объектов в их экспоненты, а вместе с eval мы можем установить изоморфизм объектов и их экспонент.

Date: 2012-07-02 09:26 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
Что-то с экспоненциалами какая-то путаница.

Допустим есть g: A->B. Тогда curry(g) - это стрелка из A в BAxA такая, что eval.curry(g)=g

Важно, что curry нужно специфическую функцию.

Мне не совсем ясно, как у вас eval устроен и почему e1 = eval.pair(I,!).

Ок, B=1, значит, !: A->1. Тогда curry(!) - это стрелка из A в 1AxA. Здесь pair(I,!) вроде нигде не фигурирует.

А что насчёт A=1? Эммм... какую именно функцию f: 1->B вы хотите curry из 1 в B1xB? Их может быть много, обычно по одной для каждой точки B. Здесь я вообще не пойму, как пример соответствует этой ситуации, и pair(I,!) снова не вижу.

Date: 2012-07-02 09:28 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
"хотите curry из 1 в B1xB?"

тьфу, "хотите curry из 1 в B1x1

Date: 2012-07-02 11:24 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
"Тогда curry(g) - это стрелка из A в BAxA такая, что eval.curry(g)=g"

посмотрел Пирса ещё раз - ага, тут нужно немножко не так:

"Тогда curry(g) - это стрелка из A в BA такая, что eval.(curry(g) x idA)=g"

но это ничего не меняет.

Date: 2012-07-02 01:11 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
curry чуть иначе определена, смотрите на процитированный вывод интерпретатора про exp. Там curry из функции от двух аргументов делает функцию от одного аргумента, возвращающую другую функцию от одного аргумента. Для
f0: prod(*a,*b) -> *c
---------------------------
curry(f0): *a -> exp(*b,*c)

Date: 2012-07-02 02:18 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
да, я неточно сказал. Но это всё равно не проясняет, почему eval.pair(I,!). Вы же потом пользуетесь построенными e1..3, чтобы обосновать именно вывод A^1=A.

Date: 2012-07-02 02:34 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
pair(I,!) имеет тип *a -> prod(*a,1).
eval имеет тип prod(exp(*с,*b),*с) -> *b
строим их композицию, получаем уравнение
prod(*a,1) = prod(exp(*с,*b),*с)
т.е. *b = 1
exp(*c, *b) = exp(*c, 1) = *a
тип композиции получается *a -> *c, т.е.
exp(*c, 1) -> *c
т.е. А^1 -> A.

Date: 2012-07-02 04:17 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
"строим их композицию"

сначала нужно продемонстрировать, что композицию мы можем построить, т.е. существует X такой, что A=A^X

Date: 2012-07-02 05:41 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
Не нужно. CPL позволяет соединить такие две стрелки в композицию и выводит тип - к каким объектам это применимо.

Date: 2012-07-02 06:53 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
Но тогда мы не доказываем ничего.

Например, сдаётся мне, что нужно pair(K,!), K=curry(I), у которого точно такой тип, что и у pair(I,!), но вот корректность вытекает сама собой. Как продемонстрировать, что I=curry(I), я не знаю.

Date: 2012-07-02 07:24 pm (UTC)
From: [identity profile] thedeemon.livejournal.com
cpl> let k = curry(I)
k = curry(I)
: *a -> exp(*b,prod(*a,*b))
cpl> show pair(k,!)
pair(k,!)
: *a -> prod(exp(*b,prod(*a,*b)),1)

Date: 2012-07-02 08:02 pm (UTC)
From: [identity profile] sassa-nf.livejournal.com
вот и замечательно :) при *b=1 и получаем eval.prod(k,!)=eval.(prod(exp(1,prod(*a,1)),1)), т.е. eval((Ax1)1x1)=Ax1=A

Date: 2012-07-03 05:19 am (UTC)
From: [identity profile] thedeemon.livejournal.com
cpl> show eval.prod(k,!)
eval.prod(k,!)
: prod(*a,*b) -> prod(*a,1)

Date: 2012-07-03 07:46 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
Всё правильно! Из равенства A=Ax1 вы всегда из A получаете Ax1, т.е. prod(a,b)->prod(a,1) всегда можно применить на месте prod(a,1)->prod(a,1), а последнее применить вместо a->a


K=curry(id) даже смысл имеет - это же K-комбинатор; конечно, он может игнорировать любой тип, а не только () (так что, наличие *b понятно), а вот как eval может взять A^B x 1 и получить Ax1 - загадка. В категорном смысле eval существует только для случая A^BxB. Поэтому вывод CPL "более полиморфный", чем требуется в категориях.

Date: 2012-07-03 07:51 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
грубо говоря, я показываю одну сторону утверждения, что eval.curry(id) является изоморфизмом. А вот что показывает eval.prod(id,!) - не понятно.

Хотя логикой наподобие доказательства A=Ax1, наверное, можно показать, что A=A^1 - примерно так: если взять curry(id)=eval=id, то вроде бы все законы удовлетворены, а A=A^1=(A^1)x1=Ax1

Date: 2012-07-03 08:35 am (UTC)
From: [identity profile] sassa-nf.livejournal.com
мда. внимательнее нужно с id. :)

eval.curry(id) = e1 . e2

вот теперь всё понятно :)

Profile

thedeemon: (Default)
Dmitry Popov

February 2026

S M T W T F S
12 34567
891011121314
15161718192021
22232425262728

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Mar. 9th, 2026 08:55 pm
Powered by Dreamwidth Studios