🔥

Тред (Майк Башуров)


Теперь повысим градус типизации и ФП почти до финала: поговорим о моей тайной (на самом деле не очень) любви: ReasonML Но перед этим немного вспомним flow (for a reason).

Во-первых во флоу есть полноценный вывод типов В ТС весь вывод работает вниз по АСТ (с уточнениями) Это означает что дальнейшие инструкции уже не смогут повлиять на выведенный тип То есть вывести это он может вполне const a = { field: 'str' }

И с возвращаемым типом тоже справится (хотя и не всегда) const foo(a: { field: string }) => a.field

А вот аргументов функции уже нет, потому что объявление функции должно быть до ее вызова const foo = (a) => a foo(1) // тип функции уже выведен как any => any

Так вот флоу это разрулит! Он как-бы решает огромное уравнение где типы это переменные и смотрит сходится все или нет. Если точно не сходится, возникает ошибка компиляции

Таким образом, в теории, можно вообще не писать типы! То есть статические проверки у нас есть, а типы мы не пишем! Isn't that wonderful

Дико поддержу Кто-то где-то сказал что у статической типизации ужасный UX И это правда так, типы обычно не говорят где вы совершили ошибку, они говорят что что-то не так и что конкретно не так У меня однако есть одна дикая идея как эту ситуацию можно улучшить twitter.com/rm_baad/status…
На практике конечно не все так гладко. Я уже говорил что у типизации плохой UX twitter.com/jsunderhood/st… Но используя вывод на полную катушку вы рискуете только это усугубить. Потому что теперь вы не только не понимаете в чем ошибка, но еще и где

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

Все что внутри наоборот типизировать не стоит, ибо это усложняет рефакторинг (явно аннотирование публичного контракта же упрощает)

Есть еще одна интересная особенность систем типов и систем вывода типов: чем более экпрессивна система типов, тем сложнее делать вывод типов Поэтому некоторые системы не гарантируют вывод в 100% случаев (например Haskell)

В итоге трейдоффы на трейдоффах, грустно конечно, но что поделать Тем не менее вывод типов это крутая штука и не стоит ей пренебрегать А что еще во флоу интересного?

Во флоу есть нативные opaque типы через которые также можно делать номинальные типы flow.org/en/docs/types/…

Во флоу также есть более интересные utility types, например $Call flow.org/en/docs/types/… Кто-то может возразить что в ТС есть ReturnType, но это не совсем одно и то же $Call возвращает результат вызова с определенными аргументами

То есть если функция возвращает разное в разных случаях (читай перегрузки) то флоу высчитает соответствующий тип ТС у перегрузок выводить тип возврата нормально не умеет

Это я все не к тому что ТС плохой и не к холивару, а к тому что стоит всегда интересоваться тем что происходит вне вашего типичного окружения/языка/технологии Ну и конечно чтобы плавно перейти к изюминке: ReasonML

Еще немного истории, был такой язык OCaml сделали в 1996 году. Это был (и есть) мультипарадигменный язык из ML семейства и как-то так случилось что Фейсбук (и не только) стал его активно использовать (у них например есть юникернел целиком на OCaml mirage.io)

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

Потому что им в голову пришла другая идея: а что если писать фронтенд на окамле и компилировать в жс? Идея конечно не нова, был и Elm и PureScript и F# с Fable, как избежать их ошибок? Для этого они решили что язык похожий на язык ML семейства плохо подходит

Слишком чужеродный синтаксис, слишком неприятно мапится на JS. Хорошо, а что если сделать синтаксис гораздо ближе к жс, сделать похожие на жс структуры и сделать удобный и супертонкий (а в основном и вовсе отсутствующий интероп)? И самое главное - НЕ ТЕРЯТЬ ДОВЕРИЯ КОМЬЮНИТИ

That's how Reason was born. Что радует отдельно - остается верным этому по сей день. Прежде чем мы перейдем к самому языку, хочется выделить то что выгодно отличает его от некоторых других compile2js языков

Несмотря на то что он general purpose, reason изначально придуман чтобы компилироваться в жс. Для F# есть Fable, есть Kotlin, есть ScalaJs, но это все second class citizens. Тут же все для веба и ради веба (точнее ради реакта, но об этом попозже)

Опять же несмотря на то что он для веба, он имеет практически полную AST совместимость с OCaml. То есть вы можете без потерь (за редкими исключениями) транслировать Reason в OCaml и обратно. Соотвественно так как компилятор один, вы вольны писать как на ризоне так и на окамле

А экосистема (в том числе пакетов) будет одна! На самом деле экосистемы две: веб и натив, но про натив будет потом Конечно не все так гладко и всякие проблемы будут, если вы будете стараться push it to the limit, но тем не менее

Язык и экосистема активно развиваются. Я помню как смотрел доклад Patrick Stapfer про reason в 2017, помню как начинал изучать и вижу какой он сейчас. Все сильно изменилось в сторону того что стало проще, понятней и логичнее

Комьюнити небольшое (по сравнению с тем же ТС), но фб действительно в это вкладывается. На дискорде сидят кор контрибютуры, прекрасно принимаются PR (испытал на своем опыта), куча дружелюбных людей которые делают крутые вещи

Это те причины почему я верю в язык и комьюнити Теперь давайте наконец-то про сам язык!

Во-первых есть полноценный вывод типов Про него я уже рассказал выше

Во-вторых есть компилятор (bucklescript.github.io) который имеет очень тонкий рантайм и стандартную библиотеку (Belt) Компилятор при этом больше похож на babel или tsc, он не делает бандлы, а просто кладет скомпилированные жс файлы рядом с оригинальными

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

В-третьих в нем есть функциональные компоненты, иммутабельные списки (в которые быстро писать в начало), рекорды и переменные (лол) по-умолчанию иммутабельные Для мутабельных переменных есть синтаксис и еще shadowing (когда вы переобъявлете переменную во вложенном скоупе)

В-четвертых в нем есть те структуры которые более похожи на жс, мутабельные массивы, Js.Dict для объекта как мапы и всякое другое Конечно же обо всем этом вам лучше расскажет документация, но я почему-то все еще здесь reasonml.github.io/docs/en/overvi…

В-пятых в нем удобный interop с js (привет Elm) reasonml.github.io/docs/en/interop Что еще прикольней так это то что помимо того что можно писать интероп с библиотеками и js файлами еще можно использовать их типы! Есть вот такая тула которая позволит генерить ризон из flow/ts и наоборот

В-шестых в нем есть first class поддержка React reasonml.github.io/reason-react/d… Вот так выглядит функциональный реакт компонент на ризоне [@react.component] let make = (~name) => <button> {ReasonReact.string("Hello " ++ name ++ "!")} </button>;

Получается семантичненько open Path let root = create("/") let myFolder = create("saito/code") let myPath = root // myFolder

Тут я использовать еще одну фичу: синтаксис открытия модуля Вообще все модули находяться в глобальной области видимости под своим неймспейсом. Можно было бы написать так let root = Path.create("/") let myFolder = Path.create("saito/code") let myPath = root Path.// myFolder

Но Path.// выглядит особенно уродливо. К счастью есть scoped open module Path.( // мы открыли модуль пока мы в () let root = create("/") let myFolder = create("saito/code") let myPath = root // myFolder )

Но опять же, это все документация и насущное, вернемся к практике Давайте о том что же в ризоне нет/сильно недостает

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

Нет async/await. Надежды есть, в окамл 4.08 завезли сахар для монад (а промис это почти монада), похожий на computation expression в F# и с помощью него можно сделать аналог async/await. Но по срокам не подскажу

Есть некоторые проблемы с динамическими импортами. Во всяком случае были год назад, тогда на ReasonConf чуваки из фб сказали что да, есть такая недоработка, дальше не следил

Но в целом только вот эти три крупные проблемы (во всяком случае которые испытал я)!

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

Тут я скину несколько ссылок Доки ризона reasonml.github.io/docs/en/what-a… Доки reason-react reasonml.github.io/reason-react/d… Доки bucklescript (компилятор в жс) bucklescript.github.io/docs/en/instal… И дискорд discordapp.com/invite/reasonml И предлагаю вам позадавать вопросы про ризон