it-swarm.com.ru

Злоупотребление алгеброй алгебраических типов данных - почему это работает?

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

Определив основные типы

  • Продукт
  • Союз +
  • Singleton X
  • Единица 1

и используя сокращение для X•X и 2X для X+X и так далее, мы можем затем определить алгебраические выражения для, например, связанные списки

data List a = Nil | Cons a (List a)L = 1 + X • L

и бинарные деревья:

data Tree a = Nil | Branch a (Tree a) (Tree a)T = 1 + X • T²

Теперь мой первый инстинкт как математика состоит в том, чтобы сходить с ума с этими выражениями и попытаться найти для L и T. Я мог бы сделать это путем повторной замены, но, кажется, гораздо проще ужасно злоупотреблять обозначениями и делать вид, что я могу изменить их по своему желанию. Например, для связанного списка:

L = 1 + X • L

(1 - X) • L = 1

L = 1 / (1 - X) = 1 + X + X² + X³ + ...

где я использовал расширение степенных рядов 1 / (1 - X) совершенно неоправданным способом для получения интересного результата, а именно, что тип L является либо Nil, либо содержит 1 элемент, либо содержит 2 элемента, либо 3 и т. д.

Будет интереснее, если мы сделаем это для бинарных деревьев:

T = 1 + X • T²

X • T² - T + 1 = 0

T = (1 - √(1 - 4 • X)) / (2 • X)

T = 1 + X + 2 • X² + 5 • X³ + 14 • X⁴ + ...

снова, используя расширение степенных рядов (сделано с помощью Wolfram Alpha ). Это выражает неочевидный (для меня) факт, что существует только одно двоичное дерево с 1 элементом, 2 двоичных дерева с двумя элементами (второй элемент может находиться слева или справа), 5 двоичных деревьев с тремя элементами и т.д. ,.

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

И, возможно, более интересно, возможно ли расширить эти идеи? Существует ли теория алгебры типов, которая допускает, например, произвольные функции типов, или для типов требуется представление степенного ряда? Если вы можете определить класс функций, то имеет ли какое-либо значение композиция функций?

273
Chris Taylor

Отказ от ответственности: Многое из этого не совсем правильно работает, когда вы принимаете во внимание ⊥, поэтому я буду просто игнорировать это ради простоты.

Несколько начальных пунктов:

  • Обратите внимание, что "союз", вероятно, не лучший термин для A + B здесь - это конкретно a disjoint union из двух типов, потому что два стороны различаются, даже если их типы одинаковы. Для чего это стоит, более распространенный термин просто "тип суммы".

  • Синглтон-типы - это фактически все типы юнитов. Они ведут себя одинаково при алгебраических манипуляциях, и, что более важно, объем присутствующей информации все еще сохраняется.

  • Вы, вероятно, хотите нулевой тип, а также. Стандартного имени нет, но наиболее распространенное имя - Void. Нет значений, тип которых равен нулю, так же как есть одно значение, тип которого равен единице.

Здесь все еще не хватает одной важной операции, но я вернусь к этому через минуту.

Как вы, наверное, заметили, Haskell имеет тенденцию заимствовать понятия из теории категорий, и все вышеперечисленное имеет очень простую интерпретацию как таковую:

  • Для объектов A и B в Hask их произведение A × B является уникальным (с точностью до изоморфизма) типом, который допускает две проекции fst : A × B → A и snd : A × B → B, где заданы любой тип C и функции f : C → A, g : C → B вы можете определить сопряжение f &&& g: C → A × B такой, что fst ∘ (f &&& g) = f и аналогично для г . Параметричность гарантирует универсальные свойства автоматически, и мой менее тонкий выбор имен должен дать вам представление. Кстати, оператор (&&&) определен в Control.Arrow.

  • Двойственным из вышеперечисленного является побочный продукт A + B с инъекциями inl : A → A + B и inr : B → A + B, где дан любой тип C и функции f : A → C, g : B → C, вы можете определить сопряжение f ||| g: A + B → C, так что очевидные эквивалентности выполнены. Опять же, параметричность гарантирует большинство сложных деталей автоматически. В этом случае стандартными инъекциями являются просто Left и Right, а сопряжение - это функция either.

Многие из свойств типа продукта и суммы могут быть получены из вышеупомянутого. Обратите внимание, что любой одноэлементный тип является терминальным объектом Hask , а любой пустой тип является начальным объектом.

Возвращаясь к вышеупомянутой отсутствующей операции, в декартова замкнутая категория у вас есть экспоненциальные объекты , которые соответствуют стрелкам категории. Наши стрелки являются функциями, наши объекты являются типами с видом *, а тип A -> B действительно ведет себя как B в контексте алгебраического манипулирования типами. Если не понятно, почему это должно выполняться, рассмотрите тип Bool -> A. Имея только два возможных входа, функция этого типа изоморфна двум значениям типа A, т.е. (A, A). Для Maybe Bool -> A у нас есть три возможных входа и так далее. Кроме того, обратите внимание, что если мы перефразируем приведенное выше определение сопряжения для использования алгебраической записи, мы получим тождество C × CВ = CА + В,.

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

  • Тип продукта (A, B) представляет значение, каждое из A и B, взятых независимо. Таким образом, для любого фиксированного значения a :: A существует одно значение типа (A, B) для каждого обитателя B. Это, конечно, декартово произведение, а число жителей вида продукта является произведением числа жителей факторов.

  • Тип суммы Either A B представляет значение из A или B с выделением левой и правой ветвей. Как упоминалось ранее, это несвязанный союз, и число жителей типа суммы является суммой числа жителей слагаемых.

  • Экспоненциальный тип B -> A представляет сопоставление значений типа B со значениями типа A. Для любого фиксированного аргумента b :: B ему может быть присвоено любое значение A; значение типа B -> A выбирает одно такое сопоставление для каждого входа, что эквивалентно произведению такого количества копий A, сколько B имеет жителей, отсюда возведение в степень.

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

С другой стороны, приведенные выше конструкции проводят много времени, говоря о подсчете жителей и перечислении возможных значений типа это полезная концепция здесь. Это быстро приводит нас к перечислительная комбинаторика , и если вы обратитесь к связанной статье в Википедии, вы обнаружите, что первое, что она делает, - это определение "пар" и "союзов" в том же смысле, что и Типы product и sum посредством производящие функции , затем делают то же самое для "последовательностей", которые идентичны спискам на Haskell, используя ту же технику, что и вы.


Редактировать: О, и вот быстрый бонус, который, я думаю, наглядно демонстрирует это. Вы упомянули в комментарии, что для типа дерева T = 1 + T^2 вы можете получить идентификатор T^6 = 1, что явно неверно. Однако T^7 = T выполняется , и биекция между деревьями и семью корнями деревьев может быть построена напрямую, ср. "Семь деревьев в одном" Андреаса Бласса .

Редактировать × 2: Что касается конструкции "производная типа", упомянутой в других ответах, вам также может понравиться эта статья из тот же автор , который опирается на идею дальше, в том числе понятия деления и другие интересные вещи.

135
C. A. McCann

Двоичные деревья определяются уравнением T=1+XT^2 в полукольце типов. По построению T=(1-sqrt(1-4X))/(2X) определяется тем же уравнением в полукольце комплексных чисел. Итак, учитывая, что мы решаем одно и то же уравнение в одном и том же классе алгебраической структуры, на самом деле не должно удивлять, что мы видим некоторое сходство.

Подвох в том, что когда мы размышляем о полиномах в полукольце комплексных чисел, мы обычно используем тот факт, что комплексные числа образуют кольцо или даже поле, поэтому мы используем такие операции, как вычитание, которые не применяются к полукольцам. Но мы часто можем исключить вычитания из наших аргументов, если у нас есть правило, которое позволяет нам отменять обе части уравнения. Это доказывает то, что Fiore and Leinster показывает, что многие аргументы о кольцах могут быть переданы в полукольца.

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

Однако это еще не все. Одно дело доказать, что два типа равны (скажем), показав, что два степенных ряда равны. Но вы также можете получить информацию о типах, проверив термины в степенных рядах. Я не уверен, каким должно быть официальное заявление. (Я рекомендую Brent Yorgey's статья на комбинаторные виды для некоторых работ, которые тесно связаны, но виды нет так же, как типы.)

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

Повеселись!

43
sigfpe

Кажется, что все, что вы делаете, - это расширение отношения повторения.

L = 1 + X • L
L = 1 + X • (1 + X • (1 + X • (1 + X • ...)))
  = 1 + X + X^2 + X^3 + X^4 ...

T = 1 + X • T^2
L = 1 + X • (1 + X • (1 + X • (1 + X • ...^2)^2)^2)^2
  = 1 + X + 2 • X^2 + 5 • X^3 + 14 • X^4 + ...

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

20
newacct

У меня нет полного ответа, но эти манипуляции имеют тенденцию "просто работать". Соответствующий документ может быть Объекты категорий как сложные числа Фьоре и Ленстера - я наткнулся на этот документ, читая блог sigfpe по соответствующей теме ; остальная часть этого блога - золотая жила для подобных идей и стоит проверить!

Кстати, вы также можете различать типы данных - это даст вам подходящую молнию для этого типа данных!

18
yatima2975

Алгебра коммуникативных процессов (ACP) имеет дело с подобными выражениями для процессов. Он предлагает сложение и умножение в качестве операторов выбора и последовательности со связанными нейтральными элементами. На основании этого есть операторы для других конструкций, таких как параллелизм и нарушение. Смотрите http://en.wikipedia.org/wiki/Algebra_of_Communicating_Processes . В Интернете также есть статья под названием "Краткая история алгебры процессов".

Я работаю над расширением языков программирования с помощью ACP. В апреле прошлого года я представил исследовательский документ на Scala Days 2012, доступный по адресу http://code.google.com/p/subscript/

На конференции я продемонстрировал отладчик, выполняющий параллельную рекурсивную спецификацию пакета:

Сумка = A; (Сумка & а)

где А и стенд для ввода и вывода действий; точка с запятой и знак амперсанда означают последовательность и параллелизм. Смотрите видео на SkillsMatter, доступное по предыдущей ссылке.

Спецификация сумки более сопоставима с

L = 1 + X • L

было бы

B = 1 + X & B

ACP определяет параллелизм с точки зрения выбора и последовательности с использованием аксиом; см. статью в Википедии. Интересно, какова будет аналогия с сумкой?

L = 1/(1-X)

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

searchCommand = нажал (searchButton) + ключ (Enter)

cancelCommand = clicked (cancelButton) + ключ (Escape)

может быть записано более кратко, сделав два уточнения "нажатыми" и "ключом" неявными (как то, что Scala позволяет с функциями). Следовательно, мы можем написать:

searchCommand = searchButton + Enter

cancelCommand = cancelButton + Escape

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

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

10
André van Delft

Серия исчисления и маклаурина с типами

Вот еще одно небольшое дополнение - комбинаторное понимание того, почему коэффициенты в разложении рядов должны "работать", в частности, сосредоточение внимания на рядах, которые могут быть получены с использованием подхода Taylor-Maclaurin из исчисления. NB: пример расширения серии, который вы приводите для типа манипулируемого списка, - это серия Maclaurin.

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

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

  • мы занимаемся предоставлением толкований из одного домена субъектам из другого, и представляется целесообразным разграничить такие иностранные понятия таким образом.
  • некоторые понятия могут быть формализованы более строго, но форма и идеи кажутся более важными (и занимают меньше места для написания), чем детали.

Определение серии Маклаурин

серия Маклаурина функции f : ℝ → ℝ определяется как

f(0) + f'(0)X + (1/2)f''(0)X² + ... + (1/n!)f⁽ⁿ⁾(0)Xⁿ + ...

где f⁽ⁿ⁾ означает nth производную от f.

Чтобы понять серию Маклаурина как интерпретируемую с типами, нам нужно понять, как мы можем интерпретировать три вещи в контексте типа:

  • производная (возможно, множественная)
  • применение функции к 0
  • такие термины, как (1/n!)

и оказывается, что эти концепции из анализа имеют подходящих аналогов в мире типов.

Что я имею в виду под "подходящим партнером"? Он должен иметь вид изоморфизма - если мы сможем сохранить истину в обоих направлениях, факты, выводимые в одном контексте, могут быть перенесены в другой.

Исчисление с типами

Так что же означает производная выражения типа? Оказывается, что для большого и корректного ("дифференцируемого") класса выражений типов и функторов существует естественная операция, которая ведет себя достаточно схожим образом, чтобы быть подходящей интерпретацией!

Чтобы испортить изюминку, операция, аналогичная дифференциации, заключается в создании "контекстов с одним отверстием". This - это отличное место для дальнейшего расширения этой конкретной точки, но основная концепция контекста с одним отверстием (da/dx) заключается в том, что он представляет собой результат извлечения одного подпункта определенного типа (x) от термина (типа a), сохраняя всю другую информацию, включая ту, которая необходима для определения исходного местоположения подэлемента. Например, один из способов представления контекста с одним отверстием для списка - два списка: один для элементов, которые были до извлеченного, и один для элементов, следующих за.

Мотивация для идентификации этой операции с дифференцированием исходит из следующих наблюдений. Мы пишем da/dx для обозначения типа контекста с одним отверстием для типа a с отверстием типа x.

d1/dx = 0
dx/dx = 1
d(a + b)/dx = da/dx + db/dx
d(a × b)/dx = a × db/dx + b × da/dx
d(g(f(x))/dx = d(g(y))/dy[f(x)/a] × df(x)/dx

Здесь 1 и 0 представляют типы с ровно одним и ровно нулевым населением соответственно, а + и × представляют суммы и типы продуктов как обычно. f и g используются для представления функций типа или формирователей выражений типа, а [f(x)/a] означает операцию замены f(x) для каждого a в предыдущем выражении.

Это можно записать в стиле без точек, написав f' для обозначения производной функции типа function f, таким образом:

(x ↦ 1)' = x ↦ 0
(x ↦ x)' = x ↦ 1
(f + g)' = f' + g'
(f × g)' = f × g' + g × f'
(g ∘ f)' = (g' ∘ f) × f'

который может быть предпочтительным.

Обратите внимание: равенства можно сделать строгими и точными, если мы определим производные с использованием классов изоморфизма типов и функторов.

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

Теперь мы можем интерпретировать дифференцирование, что означает nth "производная" выражения типа dⁿe/dxⁿ? Это тип, представляющий n- контексты места: термины, которые при "заполнении" n терминами типа x дают e. Есть еще одно ключевое наблюдение, связанное с '(1/n!)', которое появится позже.

Инвариантная часть функтора типа: применение функции к 0

У нас уже есть интерпретация для 0 в мире типов: пустой тип без членов. Что это означает, с комбинаторной точки зрения, применить к нему функцию типа? Если говорить более конкретно, предположим, что f является функцией типа, как выглядит f(0)? Что ж, у нас, конечно, нет доступа ни к чему типу 0, поэтому любые конструкции f(x), для которых требуется x, недоступны. Остаются те термины, которые доступны при их отсутствии, которые мы можем назвать "инвариантной" или "постоянной" частью типа.

Для явного примера возьмем функтор Maybe, который можно алгебраически представить в виде x ↦ 1 + x. Когда мы применяем это к 0, мы получаем 1 + 0 - он похож на 1: единственное возможное значение - это значение None. Для списка, аналогично, мы получаем только термин, соответствующий пустому списку.

Когда мы возвращаем его назад и интерпретируем тип f(0) как число, его можно рассматривать как количество количества терминов типа f(x) (для любого x) ) можно получить без доступа к x: то есть количеству "пустых" терминов.

Собираем все вместе: полная интерпретация серии Маклаурин

Боюсь, я не могу представить себе подходящую прямую интерпретацию (1/n!) как типа.

Однако если мы рассмотрим тип f⁽ⁿ⁾(0) в свете вышесказанного, мы увидим, что его можно интерпретировать как тип n- место контекстов для термина типа f(x), который еще не содержит x - то есть, когда мы "интегрируем" их n раз, результирующий термин будет иметь точно nxs, Не больше, не меньше. Тогда интерпретация типа f⁽ⁿ⁾(0) как числа (как в коэффициентах ряда Маклаурина f) представляет собой просто подсчет количества таких пустых n- контекстов места. Мы почти у цели!

Но где же кончается (1/n!)? Изучение процесса типа "дифференцирование" показывает нам, что при многократном применении он сохраняет "порядок", в котором извлекаются подтермы. Например, рассмотрим термин (x₀, x₁) типа x × x и операцию "проделать дыру" в нем дважды. Мы получаем обе последовательности

(x₀, x₁)  ↝  (_₀, x₁)  ↝  (_₀, _₁)
(x₀, x₁)  ↝  (x₀, _₀)  ↝  (_₁, _₀)
(where _ represents a 'hole')

хотя оба они имеют один и тот же термин, потому что есть 2! = 2 способов взять два элемента из двух, сохраняя порядок. В общем, есть n! способы получения n элементов из n. Таким образом, чтобы получить количество конфигураций типа функтора, которые имеют элементы n, нам нужно посчитать тип f⁽ⁿ⁾(0) и поделить на n!, точно как в коэффициентах ряда Маклаурина.

Таким образом, деление на n! оказывается интерпретируемым просто как само по себе.

Заключительные мысли: "рекурсивные" определения и аналитичность

Сначала несколько замечаний:

  • если функция f: ℝ → ℝ имеет производную, эта производная единственна
  • аналогично, если функция f: ℝ → ℝ аналитическая, она имеет ровно один соответствующий ряд полиномов

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

L(X) ≅ 1 + X × L(X)
L'(X) = X × L'(X) + L(X)

и тогда мы можем оценить

L'(0) = L(0) = 1

получить коэффициент в серии Maclaurin.

Но поскольку мы уверены, что эти выражения действительно строго "дифференцируемы", хотя бы неявно, и поскольку мы имеем соответствие с функциями ℝ → ℝ, где производные, безусловно, уникальны, мы можем быть уверены, что даже если мы получим значения с помощью незаконные операции, результат действителен.

Теперь, аналогично, чтобы использовать второе наблюдение, из-за соответствия (это гомоморфизм?) Функциям ℝ → ℝ, мы знаем, что, если мы удовлетворены тем, что функция имеет ряд Маклаурина, если мы можем найти какой-либо ряд вообще , принципы, изложенные выше, могут быть применены, чтобы сделать его строгим.

Что касается вашего вопроса о композиции функций, я полагаю, что правило цепочки дает частичный ответ.

Я не уверен, к какому числу ADT в стиле Haskell это относится, но я подозреваю, что их много, если не все. Я нашел поистине изумительное доказательство этого факта, но этот запас слишком мал, чтобы его содержать ...

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

Резюме: TL; DR

  • тип "дифференциация" соответствует " создание дыры ".
  • применение функтора к 0 дает нам "пустые" термины для этого функтора.
  • Maclaurin степенные ряды, поэтому (несколько) строго соответствуют перечислению числа членов типа функтора с определенным количеством элементов.
  • неявная дифференциация делает это более водонепроницаемым.
  • уникальность производных и уникальность степенных рядов означает, что мы можем выдумать детали, и это работает.
6
Oly

Теория зависимого типа и функции произвольного типа

Мой первый ответ на этот вопрос был высоким по понятиям и низким по деталям и отраженным в подвопросе "что происходит?"; этот ответ будет таким же, но сфокусирован на вопросе: "можем ли мы получить функции произвольного типа?".

Одним из расширений алгебраических операций суммы и произведения являются так называемые "большие операторы", которые представляют сумму и произведение последовательности (или, в более общем случае, сумму и произведение функции в домене), обычно записываемые соответственно Σ и Π , Смотрите Сигма нотация .

Итак, сумма

a₀ + a₁X + a₂X² + ...

может быть написано

Σ[i ∈ ℕ]aᵢXⁱ

где a - это, например, некоторая последовательность действительных чисел. Продукт будет представлен аналогично с Π вместо Σ.

Когда вы смотрите вдаль, выражение такого типа очень похоже на "произвольную" функцию в X; конечно, мы ограничены выразимыми рядами и связанными с ними аналитическими функциями. Это кандидат на представление в теории типов? Определенно!

Класс теорий типов, которые непосредственно представляют эти выражения, является классом теорий "зависимых" типов: теорий с зависимыми типами. Естественно, у нас есть термины, зависящие от терминов, и в таких языках, как Haskell, с функциями типов и определением типов, терминами и типами в зависимости от типов. В зависимом окружении у нас также есть типы в зависимости от условий. Haskell не является языком с зависимой типизацией, хотя многие свойства зависимых типов могут быть смоделированы немного мучая язык .

Карри-Ховард и зависимые типы

"Изоморфизм Карри-Говарда" начал свою жизнь как наблюдение, что термины и правила оценки типов лямбда-исчисления с простыми типами в точности соответствуют естественному дедукции (сформулированной Генценом), примененной к интуиционистской логике высказываний, с типами, заменяющими высказывания. и термины, занимающие место доказательств, несмотря на то, что оба были изобретены/открыты независимо. С тех пор это стало огромным источником вдохновения для теоретиков типов. Одна из наиболее очевидных вещей, которую следует рассмотреть, - может ли и как это соответствие для логики высказываний быть распространено на логики предикатов или логики более высокого порядка. Теории зависимого типа изначально возникли на этом пути исследования.

Для введения в изоморфизм Карри-Ховарда для простейшего типа лямбда-исчисления см. здесь . Например, если мы хотим доказать A ∧ B, мы должны доказать A и доказать B; комбинированное доказательство - это просто пара доказательств: по одному на каждое соединение.

При естественном удержании:

Γ ⊢ A    Γ ⊢ B
Γ ⊢ A ∧ B

и в простом наборе лямбда-исчисление:

Γ ⊢ a : A    Γ ⊢ b : B
Γ ⊢ (a, b) : A × B

Аналогичные соответствия существуют для и типов суммы, и типов функций, а также для различных правил исключения.

Недоказуемое (интуитивно ложное) суждение соответствует необитаемому типу.

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

Учитывая только конструктивные доказательства, что представляет собой доказательство ∀x ∈ X.P(x)? Мы можем думать об этом как о функции доказательства, принимая термины (x) к доказательствам их соответствующих предложений (P(x)). Таким образом, члены (доказательства) типа (предложения) ∀x : X.P(x) являются "зависимыми функциями", которые для каждого x в X дают термин типа P(x).

Как насчет ∃x ∈ X.P(x)? Нам нужен любой член X, x, вместе с доказательством P(x). Таким образом, члены (доказательства) типа (предложения) ∃x : X.P(x) являются "зависимыми парами": выделенный термин x в X вместе с термином типа P(x).

Запись: я буду использовать

∀x ∈ X...

для фактических утверждений о членах класса X, и

∀x : X...

для выражений типа, соответствующих универсальному количественному определению типа X. Аналогично для .

Комбинаторные соображения: продукты и суммы

Наряду с соответствием типов по Карри-Говарду с предложениями мы имеем комбинаторное соответствие алгебраических типов с числами и функциями, что является основным вопросом этого вопроса. К счастью, это можно распространить на зависимые типы, описанные выше!

Я буду использовать обозначение модуля

|A|

представлять "размер" типа A, чтобы сделать явным соответствие, обозначенное в вопросе, между типами и числами. Обратите внимание, что это понятие вне теории; Я не утверждаю, что в языке должен быть такой оператор.

Давайте посчитаем возможные (полностью редуцированные, канонические) члены типа

∀x : X.P(x)

это тип зависимых функций, принимающих термины x типа X в термины типа P(x). Каждая такая функция должна иметь вывод для каждого члена X, и этот вывод должен быть определенного типа. Тогда для каждого x в X это дает |P(x)| "выборы" вывода.

Изюминка

|∀x : X.P(x)| = Π[x : X]|P(x)|

что, конечно, не имеет большого смысла, если X равен IO (), но применимо к алгебраическим типам.

Точно так же термин типа

∃x : X.P(x)

это тип пар (x, p) с p : P(x), поэтому с учетом любого x в X мы можем создать подходящую пару с любым членом P(x), предоставив |P(x)| 'choices'.

Следовательно,

|∃x : X.P(x)| = Σ[x : X]|P(x)|

с такими же предостережениями.

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

Мы приближаемся!

Векторы: представление зависимых кортежей

Можем ли мы теперь кодировать числовые выражения, такие как

Σ[n ∈ ℕ]Xⁿ

как выражения типа?

Не совсем. Хотя мы можем неофициально рассмотреть значение таких выражений, как Xⁿ в Haskell, где X - это тип, а n - натуральное число, это злоупотребление нотацией; это выражение типа, содержащее число: отчетливо не допустимое выражение.

С другой стороны, с зависимыми типами на картинке, типы, содержащие числа, это как раз и есть точка; на самом деле, зависимые кортежи или "векторы" являются очень часто цитируемым примером того, как зависимые типы могут обеспечивать прагматическая безопасность на уровне типов для таких операций, как доступ к списку . Вектор - это просто список вместе с информацией уровня типа относительно его длины: именно то, что мы ищем для выражений типа, таких как Xⁿ.

Для продолжительности этого ответа, пусть

Vec X n

быть типом длины -n векторов X- значений типа.

Технически n здесь, а не фактическое натуральное число, является представлением в системе натурального числа. Мы можем представлять натуральные числа (Nat) в стиле Peano как ноль (0) или как преемник (S) другого натурального числа, а для n ∈ ℕ я пишу ˻n˼ для обозначения термина в Nat, который представляет n. Например, ˻3˼ - это S (S (S 0)).

Тогда у нас есть

|Vec X ˻n˼| = |X|ⁿ

для любого n ∈ ℕ.

Типы Nat: продвижение ℕ терминов в типы

Теперь мы можем кодировать выражения как

Σ[n ∈ ℕ]Xⁿ

как типы. Это конкретное выражение приведет к типу, который, конечно, изоморфен типу списков X, как указано в вопросе. (Не только это, но и с точки зрения теории категорий, функция типа - которая является функтором - перевод X к указанному выше типу - это ( естественно изоморфный к функтору List.)

Последний фрагмент головоломки для "произвольных" функций - это то, как

f : ℕ → ℕ

выражения как

Σ[n ∈ ℕ]f(n)Xⁿ

так что мы можем применить произвольные коэффициенты к степенному ряду.

Мы уже понимаем соответствие алгебраических типов числам, что позволяет нам сопоставлять типы с числами и функции типов с числовыми функциями. Мы также можем пойти другим путем! - если взять натуральное число, то существует явно определяемый алгебраический тип с таким количеством членов-членов, независимо от того, есть у нас зависимые типы или нет. Мы можем легко доказать это вне теории типов по индукции. Нам нужен способ отображения натуральных чисел на типы внутри системы.

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

Существует несколько способов представления типов на уровне терминов. Я буду использовать здесь воображаемую нотацию по Хаскеллу с * для вселенной типов, которая обычно считается типом в зависимом окружении.1

Аналогичным образом, существует также как минимум столько же способов обозначения '- elmination', сколько существует зависимых теорий типов. Я буду использовать Haskellish для сопоставления с образцом.

Нам нужно сопоставление α от Nat до * со свойством

∀n ∈ ℕ.|α ˻n˼| = n.

Следующее псевдоопределение достаточно.

data Zero -- empty type
data Successor a = Z | Suc a -- Successor ≅ Maybe

α : Nat -> *
α 0 = Zero
α (S n) = Successor (α n)

Итак, мы видим, что действие α отражает поведение преемника S, делая его своего рода гомоморфизмом. Successor - это функция типа, которая "добавляет единицу" к числу членов типа; то есть |Successor a| = 1 + |a| для любого a с определенным размером.

Например, α ˻4˼ (то есть α (S (S (S (S 0))))), это

Successor (Successor (Successor (Successor Zero)))

и условия этого типа

Z
Suc Z
Suc (Suc Z)
Suc (Suc (Suc Z))

давая нам ровно четыре элемента: |α ˻4˼| = 4.

Аналогично, для любого n ∈ ℕ мы имеем

|α ˻n˼| = n

как требуется.

  1. Многие теории требуют, чтобы члены * были просто представителями типов, а операция предоставляется в виде явного отображения терминов типа * в связанные с ними типы. Другие теории допускают, чтобы сами литеральные типы были сущностями уровня термина.

"Произвольные" функции?

Теперь у нас есть аппарат для выражения общего общего степенного ряда в виде типа!

Сериал

Σ[n ∈ ℕ]f(n)Xⁿ

становится типом

∃n : Nat.α (˻f˼ n) × (Vec X n)

где ˻f˼ : Nat → Nat является некоторым подходящим представлением на языке функции f. Мы можем видеть это следующим образом.

|∃n : Nat.α (˻f˼ n) × (Vec X n)|
    = Σ[n : Nat]|α (˻f˼ n) × (Vec X n)|          (property of ∃ types)
    = Σ[n ∈ ℕ]|α (˻f˼ ˻n˼) × (Vec X ˻n˼)|        (switching Nat for ℕ)
    = Σ[n ∈ ℕ]|α ˻f(n)˼ × (Vec X ˻n˼)|           (applying ˻f˼ to ˻n˼)
    = Σ[n ∈ ℕ]|α ˻f(n)˼||Vec X ˻n˼|              (splitting product)
    = Σ[n ∈ ℕ]f(n)|X|ⁿ                           (properties of α and Vec)

Насколько это "произвольно"? Этот метод ограничен не только целыми коэффициентами, но и натуральными числами. Кроме того, f может быть чем угодно, учитывая язык Turing Complete с зависимыми типами, мы можем представить любую аналитическую функцию с натуральными числовыми коэффициентами.

Я не исследовал взаимодействие этого с, например, случаем, представленным в вопросе о List X ≅ 1/(1 - X), или о том, какой возможный смысл могут иметь такие отрицательные и нецелые "типы" в этом контексте.

Надеемся, что этот ответ каким-то образом исследует, как далеко мы можем зайти с функциями произвольного типа.

4
Oly