Linear types can change the world!
Philip Wadler
Вспомним, где в программировании встречаются линейные функции. Программистам знакомы понятия линейной, квадратичной, логарифмической и т.д. сложности. Речь идёт об оценке сложности алгоритмов. Линейная сложность означает, что мы можем оценить количество операций линейной функцией (от количества элементов контейнера, например). И замечательным образом процедура, которая использует свой аргумент ровно один раз, будет обладать линейной сложностью относительно количества структурных элементов аргумента. Грубо говоря, процедура, которая принимает линейный список, сможет пройтись по элементам этого списка только один раз, что соответствует линейной сложности относительно размера списка. Это означает, что немного модифицировав правила линейной логики, можно получить языки программирования с ограничением на алгоритмическую сложность процедур. Сам Жирар предложил несколько систем типов ‒ ограниченную линейную логику (bounded linear logic, BLL), легкую линейную логику (light linear logic), элементарную линейную логику (elementary linar logic. ELL). Пока они интересны только с исследовательской точки зрения, но возможно в будущем появятся полноценные языки программирования на их основе.
Прохождение по списку в функциональном стиле |
Линейность обеспечивает очень важную вещь с точки зрения программирования ‒локальность. Если про переменную знает только одна функция, то для такой переменной не нужна сборка мусора (в том числе в виде подсчёта ссылок). Не нужны критические секции, чтоб контролировать к ней доступ в многопоточной среде ‒ ведь никто кроме нас и не будет к ней обращаться. Локальность данных делает код ясным и для программиста и для компилятора.
Кстати о параллельном программировании. Одним из применений линейной логики является описание взаимодействующих агентов. Вернее, не совсем так. Протоколы взаимодействия описываются с помощью систем сессионных типов. Но сессионные типы глубоко связаны с линейной логикой: формула линейной логики представляет собой протокол, который стороны должны соблюдать при общении, процедура избавления от лемм в доказательстве соответствует самому общению. Такое соответствие помогло построить систему типов, в которой соблюдение протокола гарантирует отсутствие взаимных блокировок. И кажется, что естественной вычислительной интерпретацией классической линейной логики являются именно такие модели. Во всяком случае в них легко интерпретировать классические дуальности линейной логики. Но этому надо будет посвятить отдельный рассказ.
Кстати о параллельном программировании. Одним из применений линейной логики является описание взаимодействующих агентов. Вернее, не совсем так. Протоколы взаимодействия описываются с помощью систем сессионных типов. Но сессионные типы глубоко связаны с линейной логикой: формула линейной логики представляет собой протокол, который стороны должны соблюдать при общении, процедура избавления от лемм в доказательстве соответствует самому общению. Такое соответствие помогло построить систему типов, в которой соблюдение протокола гарантирует отсутствие взаимных блокировок. И кажется, что естественной вычислительной интерпретацией классической линейной логики являются именно такие модели. Во всяком случае в них легко интерпретировать классические дуальности линейной логики. Но этому надо будет посвятить отдельный рассказ.
Вернёмся пока к последовательному исполнению. Кроме возможности обновлять данные линейная логика может помочь с другими возможностями, присущими императивным языкам. А они нужны, ведь у чисто функционального языка есть один недостаток: никто не будет использовать такой язык для прикладного программирования. Кому нужен язык, который даже не может вывести данные в консоль? Возникает вопрос, как интегрировать в такой язык общение с внешним миром, сохранив при этом его математическую природу. Вероятно нам нужно обозначить отдельным типом функции взаимодействующие с внешним миром. Фактически считать, что они принимают на вход состояние мира и возвращают новое. И здесь идея линейности может пригодиться, поскольку состояние внешнего мира невозможно сохранить, скопировать, но желательно не терять².
Что интересно, идея такого использования линейных типов появилась почти одновременно с линейной логикой. Вариант линейных типов ‒ уникальные типы (uniqueness types)³ используется в языке Clean, который появился в 1988 году (работа Жирара по линейной логике опубликована в 1987). Даже не знаю, имел ли представление тогда автор языка о линейной логике.
Это лишь очень небольшая часть того, о чём можно было рассказать. Пока оставлены за скобками концепции полярности и фокусировки, связь с теорией игр, графическое представление доказательств и многое другое. Кстати, во всей серии у нес нет ни одного примера доказательства. Поэтому прежде чем рассказывать о графических или геометрических аспектах линейной логики, посмотрим как выглядят доказательства в стандартном виде.
¹ Это операция явного копирования, которой в линейной системе типов можно присвоить тип dup: !A ⊸ A. Результат является независимой копией аргумента. В Haskell линейных типов нет, но смысл в том, что мы используем результат линейно.
² Другой вариант интеграции с внешним миром используемый в Haskell можно считать попыткой описать линейные типы в языке, который их не поддерживает на прямую.
³ Уникальные типы отличаются от линейных тем, что в линейной системе типов нелинейное значение можно преобразовать в линейное, а в уникальной системе типов неуникальное значение нельзя сделать уникальным.