дыбр и типы
Nov. 25th, 2021 10:23 amДавненько я тут ничего не писал. Помнится, некоторые ЖЖ-френды после переезда в Америку или Европу с работой в больших компаниях начинали писать в ЖЖ все реже, а потом и вовсе переставали. Мне еще было любопытно, почему так получается и что с ними. Этого я так и не узнал, но и сам чуть не стал таким же. Может, потому что на работе довольно интересный проект, о котором много не расскажешь, а на другие занятия либо остается мало времени, либо хобби-проекты сейчас получаются довольно длинные, преждевременно о них рассказывать не хочется, а созревают не быстро. Может, еще потому что больше общения стало в телеграм-чатах, с телефона, сюда стал реже заглядывать.
Продолжаю работать в той же компании, для которой приехал в Лондон, но в офисе не был с марта 2020-го, мы с той поры все на удаленке. Жена нашла работу и в своем офисе не была вообще ни разу, ей прислали технику домой, так и работает.
Проект мой на работе далеко не опен-сорсный, но совсем уж секретным тоже не является, на конференциях про него немного рассказывали и упоминали не раз. Делаем мы свой небольшой in-house функциональный язык программирования, чтобы отойти от традиционных для финансового мира Экселя с SQL-ем и выражать все в коде, с гитом, тестами, код ревью и прочими полезными практиками.
Изначально язык был динамически типизирован, просто потому что так было проще сделать. Но потом решили двигаться к статическим типам, и мне довелось заняться навешиванием системы типов, тайпчекинга и вывода типов на существующий недотипизированный язык. К счастью, там все иммутабельное и без ООП (кроме нативных объектов и их полей/методов), монстр а-ля TypeScript не нужен. Поскольку уже сколько-то реального кода на нем было написано без типов, хотелось сделать хороший вывод типов, чтобы не добавлять аннотации в каждую функцию, чтобы можно было мигрировать с минимальными правками. И хотелось сохранить duck typing, но проверять его статически, т.е. нужна структурная типизация. Чтоб без лишних аннотаций можно было структуру с десятком полей передать в функцию, которой нужны лишь парочка из них. Ну и гетерогенные коллекции мне хотелось тоже разрешить, т.е. нужны union types. Перефразируя местного поэта, if you can't infer sum types and structs then I don't wanna hear no chat about typing.
Думал быстро управиться, делов-то. Начал с доволько наивного Хиндли-Милнера, но быстро наткнулся на проблемы. Вспомнил про окамловские polymorphic variants и объекты, которые казалось бы так близки к тому, что мне нужно, особенно если их использовать не только для алгебраиков, но более простые типы туда засунуть, получив union types. Сделал, но опять уткнулся в проблемы.
Напомню, что polymorphic variants в окамле это такие алгебраики, которые не нужно предварительно описывать, он сам выводит где какой набор строится или ожидается. Так, например, для
компилятор окамла выведет сам, что f принимает `A | `B и возвращает `A | `B,
функция g принимает `A | `B и возвращает список из `A | `B,
а lst это список из `A | `B | `C.
Но стоит нам попытаться добавить `C в результат g:
то тут окамл уже ругается и не пущает. Потому что недалеко ушел от ХМ, для каждой типо-переменной он отслеживает множества верхних и нижних границ (upper & lower bounds), но когда из значений мы строим список, все элементы списка должны иметь один тип, их верхние и нижние границы нужным образом пересекаются/объединяются, в итоге из-за того, что x передается в f, у него верхняя граница `A | `B (ничего другого f не принимает), и эта верхняя граница влияет на все другие элементы списка, потому `B туда поместить можно, а `C уже нельзя.
Пошел учиться дальше, видел HM(X) Одерского, где про сабтайпинг умеют, но не разобрался.
Зато нашел работы Долана про Algebraic Subtyping и вдохновился. Главная идея там - отказаться в тайпчекере от уравнений про равенства типов, и везде использовать неравенства: "А подтип Б".
Тогда в списке могут быть значения с разными и даже несовместимыми верхними границами, и это ок, т.к. результирующий тип элементов списка отличен от каждого из них и связан с ними неравенствами, он для них супертип. (`A | `B это подтип `A | `B | `C, и `C это подтип `A | `B | `C).
Нагенерить неравенств-то сравнительно несложно (хотя местами было о чем подумать, все же язык у нас отличается от тех, что в статьях), а вот решить их - уже сложнее. Подход к решению со всякими бисубституциями и автоматами у Долана мне тогда показался каким-то сложным, стал делать свое решение, думал, получится проще. В итоге, правда, если прищуриться, те же бисубституции и получились, но без захода в конечные автоматы. Потом уже в 20-м году вышли статьи про CubiML и Simple-sub, там тоже довольно похожие на мое решения, они может и выглядят разными, а суть похожа.
Некоторое время назад я делал презентацию о том, что получилось, там видно в целом, как язык выглядит и как туда типы добавляются, и есть пример, как тайпчекер думает, выводя типы для
Еще скриншоты REPL'a с примерами про вывод типов и сочетание структурной и номинальной типизации. Типы можно описывать прямо в репле, и в отличие от всяких тайпскриптов типы параметров функций тоже выводятся, насколько возможно, вывод не локальный.
( Read more... )
Продолжаю работать в той же компании, для которой приехал в Лондон, но в офисе не был с марта 2020-го, мы с той поры все на удаленке. Жена нашла работу и в своем офисе не была вообще ни разу, ей прислали технику домой, так и работает.
Проект мой на работе далеко не опен-сорсный, но совсем уж секретным тоже не является, на конференциях про него немного рассказывали и упоминали не раз. Делаем мы свой небольшой in-house функциональный язык программирования, чтобы отойти от традиционных для финансового мира Экселя с SQL-ем и выражать все в коде, с гитом, тестами, код ревью и прочими полезными практиками.
Изначально язык был динамически типизирован, просто потому что так было проще сделать. Но потом решили двигаться к статическим типам, и мне довелось заняться навешиванием системы типов, тайпчекинга и вывода типов на существующий недотипизированный язык. К счастью, там все иммутабельное и без ООП (кроме нативных объектов и их полей/методов), монстр а-ля TypeScript не нужен. Поскольку уже сколько-то реального кода на нем было написано без типов, хотелось сделать хороший вывод типов, чтобы не добавлять аннотации в каждую функцию, чтобы можно было мигрировать с минимальными правками. И хотелось сохранить duck typing, но проверять его статически, т.е. нужна структурная типизация. Чтоб без лишних аннотаций можно было структуру с десятком полей передать в функцию, которой нужны лишь парочка из них. Ну и гетерогенные коллекции мне хотелось тоже разрешить, т.е. нужны union types. Перефразируя местного поэта, if you can't infer sum types and structs then I don't wanna hear no chat about typing.
Думал быстро управиться, делов-то. Начал с доволько наивного Хиндли-Милнера, но быстро наткнулся на проблемы. Вспомнил про окамловские polymorphic variants и объекты, которые казалось бы так близки к тому, что мне нужно, особенно если их использовать не только для алгебраиков, но более простые типы туда засунуть, получив union types. Сделал, но опять уткнулся в проблемы.
Напомню, что polymorphic variants в окамле это такие алгебраики, которые не нужно предварительно описывать, он сам выводит где какой набор строится или ожидается. Так, например, для
let f = function `A -> `B | `B -> `A let g x = [x; f x; `B] let lst = [`A; f `A; `C]
компилятор окамла выведет сам, что f принимает `A | `B и возвращает `A | `B,
функция g принимает `A | `B и возвращает список из `A | `B,
а lst это список из `A | `B | `C.
Но стоит нам попытаться добавить `C в результат g:
let g x = [x; f x; `B; `C]
то тут окамл уже ругается и не пущает. Потому что недалеко ушел от ХМ, для каждой типо-переменной он отслеживает множества верхних и нижних границ (upper & lower bounds), но когда из значений мы строим список, все элементы списка должны иметь один тип, их верхние и нижние границы нужным образом пересекаются/объединяются, в итоге из-за того, что x передается в f, у него верхняя граница `A | `B (ничего другого f не принимает), и эта верхняя граница влияет на все другие элементы списка, потому `B туда поместить можно, а `C уже нельзя.
Пошел учиться дальше, видел HM(X) Одерского, где про сабтайпинг умеют, но не разобрался.
Зато нашел работы Долана про Algebraic Subtyping и вдохновился. Главная идея там - отказаться в тайпчекере от уравнений про равенства типов, и везде использовать неравенства: "А подтип Б".
Тогда в списке могут быть значения с разными и даже несовместимыми верхними границами, и это ок, т.к. результирующий тип элементов списка отличен от каждого из них и связан с ними неравенствами, он для них супертип. (`A | `B это подтип `A | `B | `C, и `C это подтип `A | `B | `C).
Нагенерить неравенств-то сравнительно несложно (хотя местами было о чем подумать, все же язык у нас отличается от тех, что в статьях), а вот решить их - уже сложнее. Подход к решению со всякими бисубституциями и автоматами у Долана мне тогда показался каким-то сложным, стал делать свое решение, думал, получится проще. В итоге, правда, если прищуриться, те же бисубституции и получились, но без захода в конечные автоматы. Потом уже в 20-м году вышли статьи про CubiML и Simple-sub, там тоже довольно похожие на мое решения, они может и выглядят разными, а суть похожа.
Некоторое время назад я делал презентацию о том, что получилось, там видно в целом, как язык выглядит и как туда типы добавляются, и есть пример, как тайпчекер думает, выводя типы для
foo(o) => { a = o.fred |> toStr b = o.toto a = a.length in if b then a else o.fred + 2 }
Еще скриншоты REPL'a с примерами про вывод типов и сочетание структурной и номинальной типизации. Типы можно описывать прямо в репле, и в отличие от всяких тайпскриптов типы параметров функций тоже выводятся, насколько возможно, вывод не локальный.
( Read more... )