Подтвердить что ты не робот

Как избежать создания системы типа ad-hoc на динамически типизированных языках?

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

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

Тем не менее, это должно быть неправильно. Мне кажется, что я не использую языки с динамически типизированными "правильно". Если я должен изобрести систему типов и применить ее самостоятельно, я также могу использовать язык, на котором есть типы, начинающиеся с.

Итак, мои вопросы:

  • Существуют ли существующие парадигмы программирования (для языков без типов), которые исключают необходимость использования или изобретения систем типов?
  • Существуют ли общие рекомендации по устранению проблем, которые статическая типизация решает на динамически типизированных языках (без застенчивого переосмысления типов)?

Вот конкретный пример для вас. Я работаю с datetime и timezones в erlang (динамический, сильно типизированный язык). Это общий тип данных, с которыми я работаю:

{{Y,M,D},{tztime, {time, HH,MM,SS}, Flag}}

... где {Y,M,D} является кортежем, представляющим действительную дату (все записи являются целыми числами), tztime и time являются атомами, HH,MM,SS являются целыми числами, представляющими 24-часовое время, и Flag является одним из атомов u,d,z,s,w.

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

number < atom < reference < fun < port < pid < tuple < list < bit string
4b9b3361

Ответ 1

Помимо понятия статического и динамического и сильного против слабого ввода:

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

Ваш пример находится в Erlang. У меня есть несколько рекомендаций:

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

    is_same_day(#datetime{year=Y1, month=M1, day=D1}, 
                #datetime{year=Y2, month=M2, day=D2}) -> ...
    

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

  • Обычно записывайте свой код, который он сбой, когда допущения недействительны.

  • Если это не кажется достаточно статическим, используйте typer и dialyzer, чтобы найти ошибки, которые можно найти статически, все, что останется, будет проверено во время выполнения.

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

Ответ 2

Много хороших ответов, позвольте мне добавить:

Существуют ли существующие парадигмы программирования (для языков без типов), которые исключают необходимость использования или изобретения систем типов?

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

foo({tag, ...}) -> do_something(..);
foo({tag2, ...}) -> do_something_else(..);
foo(Otherwise)  ->
    report_error(Otherwise),
    try to fix problem here...

Убейте последнее предложение и немедленно сработайте. Пусть супервизор и другие процессы выполняют очистку (вы можете использовать monitors() для процессов janitorial, чтобы знать, когда произошел сбой).

Будьте точны, однако. Написать

bar(N) when is_integer(N) -> ...

baz([]) -> ...
baz(L) when is_list(L) -> ...

если функция известна только для работы с целыми числами или списками соответственно. Да, это проверка времени выполнения, но цель - передать информацию программисту. Кроме того, HiPE, как правило, использует подсказку для оптимизации и исключает проверку типа, если это возможно. Следовательно, цена может быть меньше, чем вы думаете.

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

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

Erlang имеет dialyzer, который может использоваться для статического анализа и определения типов ваших программ. В нем не возникает столько ошибок типа, как проверка типа, например, Ocaml, но он также не будет "плакать волка": ошибка от диализатора, по-видимому, является ошибкой в ​​программе. И он не будет отклонять программу, которая может работать нормально. Простой пример:

and(true, true) -> true;
and(true, _)    -> false;
and(false, _)   -> false.

Вызов and(true, greatmistake) вернет false, но система статического типа отклонит программу, потому что она выведет из первой строки, что сигнатура типа принимает значение boolean() в качестве второго параметра. Диализатор будет воспринимать эту функцию в противоположность и дать ей подпись (boolean(), term()) → boolean(). Он может это сделать, потому что нет необходимости защищать априори за ошибку. Если есть ошибка, система времени выполнения имеет проверку типа, которая будет ее захватывать.

Ответ 3

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

В мире Хаскелла слышится много сложного, иногда до точки страха, теминологии. Тип классов. Параметрический полиморфизм. Обобщенные типы алгебраических данных. Тип семейств. Функциональные зависимости. язык программирования Ωmega принимает его еще больше, в том числе веб-сайт, в котором перечислены "функции уровня типа" и "уровень полиморфизма".

Что это? Функции добавлены в статическую типизацию, чтобы сделать ее более гибкой. Эти функции могут быть действительно классными и, как правило, элегантными и продуманными, но их часто трудно понять. Кривая обучения в сторону, системы типов часто не могут моделировать проблемы в реальном мире элегантно. Особенно хорошим примером этого является взаимодействие с другими языками (основная мотивация С# 4 dynamic функция).

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

Одна вещь, которую я узнал от изучения Haskell, заключается в том, что я могу нести уроки, полученные в отношении сильной типизации и разумного рассуждения на более слабые языки, такие как C и даже сборка, и сам "проверять тип". А именно, я могу доказать, что разделы кода правильны сами по себе, учитывая правила, которым должны следовать мои функции и значения, и предположения, которые я допускаю делать о других функциях и значениях. Когда вы отлаживаетесь, я просматриваю и снова проверяю вещи, и думаю, насколько мой подход звучит.

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

Ответ 4

Иногда данные требуют проверки. Проверка любых данных, полученных от сети, почти всегда является хорошей идеей - особенно данными из общедоступной сети. Быть параноиком здесь только хорошо. Если что-то, похожее на статическую систему, помогает это наименее болезненно, так и быть. Есть причина, по которой Erlang позволяет аннотации типов. Даже сопоставление шаблонов можно рассматривать как просто проверку динамического типа; тем не менее, это центральная особенность языка. Сама структура данных - это "тип" в Erlang.

Хорошо, что вы можете настраивать свою систему типов на свои нужды, сделать ее гибкой и умной, в то время как системы типов языков OO обычно имеют фиксированные функции. Когда используемые структуры данных неизменяемы, как только вы проверили такую ​​структуру, вы можете с уверенностью предположить, что она соответствует вашим ограничениям, как при статической типизации.

Нет смысла готовиться к обработке любых данных в любой точке программы, динамически типизированной или нет. "Динамический тип" - это, по сути, объединение всех возможных типов; ограничение его на полезное подмножество является допустимым способом программирования.

Ответ 5

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

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

Erlang имеет очень хороший инструмент для указания и статической проверки множества типов - dialyzer: система типа Erlang для ссылок.

Так что не переделывайте типы, используйте инструменты ввода, которые уже предоставляет Erlang, для обработки типов, которые уже существуют в вашей программе (но которые вы еще не указали).

И это само по себе не устранит проверки диапазона, к сожалению. Без большого специального соуса вам действительно нужно принудительно применять это по соглашению (и умным конструкторам и т.д.), Или вернуться к проверкам времени выполнения, или и тем и другим.