Объясните мне, как вы для себя разобрались в моделях типизаций — они же все размыты

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.


Когда я был начинающим, я мог писать простые приложения на C# и C++. Долго игрался с консольными прогами, пощупал десктопные, и в какой-то момент захотел сделать сайт. Меня ждал большой сюрприз — чтобы делать сайты, одного сишарпа мало. Надо ещё знать жс, хтмл, цсс и прочую фронтовую хрень. Я потратил около недели на эти вещи, и понял — не мое. Я мог написать какой то код на джаваскрипт, но он не содержал типов, и я никак не мог взять в толк — как к этому вообще подходить. Это какое-то игрушечное программирование. Ну и забросил к чертям.


Уже потом, работе на третьей, меня перевели в отдел, где делали веб. Я подумывал уволиться, но мне объяснили — там тайпскрипт, тайпскрипт — это такой сишарп для браузера.


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


Самих параметров типизаций несколько.


Есть статическая/динамическая типизация. Статическая — это когда типы данных известны на этапе компиляции. Компилятор знает типы переменных, и на их основании проверяет корректность программы. Динамическая — это когда типы известны только на стадии выполнения, и компилятор при сборке не проверяет ничего.


При этом все статически типизированные ЯП, которые я использовал, дают возможности для динамической типизации. Any в TS, Dynamic в сишарпе. В конце концов тот же тип Object — по идее, я запихиваю в него все что угодно, и говорю, что у меня статически типизированный код. Но строго говоря, это не совсем так. Потому что я могу принять Object извне, и покастить его к чему угодно, не делая никаких проверок — это ли не элемент дин типизации?


Я не знаю, делает ли язык более динамическим возможность динтипизировать куски кода. Если VSCode статически чекает мой js код, в котором даже определений типов нет — делает ли это джаваскрипт статически типизированным? А если да, то как тогда это ужать в бинарный параметр?


А ведь это для меня самое важное — я не пишу код на динамически типизированных ЯП, потому что хочу, что бы компилятор работал за меня.


Когда я начинал писать на тайпскрипте, я знал, что у него статическая типизация. Тогда я не понимал, что это значит, и думал что статическая — это как в сишарпе. Я называл её строгой. Я писал свой код на ts, и не понимал — какого хрена происходит. Почему я объявил тип


type Lead = {
  id: string
}

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


Номинативная — это когда для нас значимо имя типа, его номинал. А структурная — это когда мы проверяем типы не по именам, а по их свойствам. То-есть, если у меня есть вот такие классы


class A {
 string name;
}

class B {
 string name;
}

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


Дело в том, что теоретически номинативная даёт больше гарантий. Но это сложные гарантии. Если я написал функцию, которая работает с классом А из примера, то и с инстансом класса B она отработает корректно — в том смысле, что она не свалится на этапе исполнения с ошибкой. Поэтому, когда я программирую на языке со структурной типизацией, я пишу функцию, и говорю — вот она работает с чем угодно, у чего есть вот такие поля или методы.


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


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


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


Я так до конца и не понял несколько важных моментов. Во первых, вот структурная типизация — она же может проверять типы на полное соответствие и на достаточное. Допустим у меня есть такой код


type A = {
  id: string
}

type B = {
  id: string;
  name: string;
}

function test(a: A) {}

Тайпскрипт разрешит мне передать в test инстанс B — потому что его структура достаточна с точки зрения типа А. Но по идее, я ведь могу захотеть такую модель типизации, которая будет структурной, но при этом не разрешит считать B удовлетворяющим A. Я не знаю, есть ли такая типизация хоть где-то, нужна ли она, и как бы она называлась.


Причем теоретически, я могу заставить тайпскрипт валидировать полное соответствие — как тогда мне называть его модель типизации? Понятия не имею.


Мой друг Паша как то сказал мне, что в тайпскрипте типизация — дерьмо. Я ответил, что в тайпскрипе я могу написать тип, который описывает число меньше ста, и получу компайл тайм гарантию, что число было проверено. А в сишарпе я так сделать не смогу. Паша впечатлился. Но он тоже прав — в куче кейсов типизация в ts — дырявая.


Есть сильная/слабая типизация. Её легко спутать со структурной/номинативной, но это разные вещи. Этот параметр определяет, делает ли среда исполнения неявные касты из одного типа в другой. Я не скажу за все япы, но все, на которых писал код я, иногда делают такие касты. То есть я не знаю ни одного абсолютно сильно типизированного языка программирования, и поэтому, когда мы говорим "слаботипизированный" — мы имеем в виду такой, который делает такие касты до неприличия часто.


Понятие очень размытое, например считающийся строгим сишарп позволяет программисту описать имплиситный каст например Собаки к инту. За 7 лет моей работы с дотнет стеком я ни разу не встречал кода, который дефайнил бы имплиситные касты — к чести моих коллег — но сам язык это позволяет.


Для меня этот параметр не особо принципиален — наверно потому, что совсем уж слабо типизированнах япов на рынке нет.


Когда меня спрашивают, сильная ли типизация в C# я говорю да. А когда спрашивают то же самое про тайпскрипт, я говорю нет. Но на деле, оба этих языка содержат имплиситные касты, и большой вопрос, в каком их больше. Я не знаю, какой тут алгоритм — как определить, сильная или строгая типизация у того или иного языка. Я даже не знаю, какая она должна быть, если честно. Думаю, меня вполне устроит, если функция, которая ждет строку, получит число и скастит его к строке, и думаю, меня совсем не устроит, если компилятор скастит к строке например boolean.


Есть ещё один параметр типизации, для которого я не знаю подходящего термина. Допустим у меня есть вот такой тип


type Person = {
  id: string;
  name: string;
}

Есть языки программирования, которые потащат в рантайм метаинформацию этого типа. Например C# позволяет на этапе исполнения посмотреть, какие поля и каких типов есть у такого-то класса. Это важная вещь — так я могу автоматизировать валидацию сторонних данных. А вот в тайпскрипте вся информация о типах есть только на этапе компиляции. Я раньше путал этот параметр с номинативной/структурной, но по факту я могу себе представить структурный яп, который тащит метаинформацию в рантайм.


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


Это навело меня на мысль, что весь вопрос в комбинации этих параметров.


Вот тайпскрипт — статический, слабый, структурный и не тащит типы в рантайм. Такая конфигурация дает много плюсов — мы пишем гибкий код, у нас сохраняется проверка соответствия программы в компилтайме, наши доменные модули легко переносимы куда угодно. И у нас на порядок меньше бойлерплейта по приведению типов друг другу, чем в том же C#.


Более того, команда разработки тайпскрипта по сути работает только над статическим анализом — и это позволяет им сделать его по-настоящему мощным. Во всяких сишарпах, джавах, котлинах и т.д. невозможно сделать на этапе компиляции вещи, которые для тайпскрипта обыденность. Я имею в виду магию с conditional и mapped types. Я никогда не достигну такого уровня проверки корректности на этапе компиляции в C#.


Но есть гигантский минус. При кодировании на тайпскрипте система типов защищает код на тайпскрипте от кода на тайпскрипте — и никак не помогает с внешними данными. Получается, что если я описал тип User, и получил список таких юзеров из JSON, мне придётся руками перечислять все свойства, и делать все проверки. Это объективно — говно.


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


В этом смысле, тайпскрипт ведет себя так же, как и все остальные языки — когда у их системы типов есть недостатки, они используют костыли, чтобы их спрятать. Чтобы спрятать недостатки сишарпа, придумали AutoMapper. Я не буду говорить про те инструменты, которые есть на рынке, чтобы скрыть недостатки Java — вы и сами все знаете, эти джависты использовали все лазейки, которые только можно было, чтобы убежать от бойлерплейта.


Вот так и в тайпскрипте. С помощью хитрых костылей мы можем заставить его вести себя как номинативный ЯП — есть брендированные типы, есть приватные тайпгарды у классов. А ещё мы можем написать ещё больше костылей, и сделать себе инструмент для протаскивания типов в рантайм. Есть крутая либа — runtypes.ts, которая позволяет описать свои типы как объекты, выводить из них обычные тайпскриптовые типы, и при этом сохранять номинативные и рантайм проверки.


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


Что ещё больше размывает границы всех этих типизационных терминов.


Вот F# — номинативно типизированный ЯП. Но. Но. В нем можно написать функцию, которая работает с чем угодно, у чего есть такое-то поле, такого-то типа. Более того, я могу весь свой код писать в таком стиле — это будет структурно типизированный код на номинативно типизированном F#. Как с этим быть?


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


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


Смотрите мой подкаст
Источник: https://habr.com/ru/post/506088/


Интересные статьи

Интересные статьи

SWAP (своп) — это механизм виртуальной памяти, при котором часть данных из оперативной памяти (ОЗУ) перемещается на хранение на HDD (жёсткий диск), SSD (твёрдотельный накоп...
Недавно завершился «Диалог 2020», международная научная конференция по компьютерной лингвистике и интеллектуальным технологиям. Традиционно одно из ключевых событий конференции – это ...
Бизнес-смыслы появились в Битриксе в начале 2016 года, но мало кто понимает, как их правильно использовать для удобной настройки интернет-магазинов.
В статье описаны необходимые параметры сервера для оптимальной работы сайта на платформе 1С-Битрикс.
Лояльность сотрудника работодателю выражается в положительном и доверительном отношении сотрудника к компании, искреннем желании трудиться и иногда выполнять даже больше, чем указано в должностно...