Теперь повысим градус типизации и ФП почти до финала: поговорим о моей тайной (на самом деле не очень) любви: 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
И предлагаю вам позадавать вопросы про ризон