Динамическая типизация что это
Ликбез по типизации в языках программирования
Эта статья содержит необходимый минимум тех вещей, которые просто необходимо знать о типизации, чтобы не называть динамическую типизацию злом, Lisp — бестиповым языком, а C — языком со строгой типизацией.
В полной версии находится подробное описание всех видов типизации, приправленное примерами кода, ссылками на популярные языки программирования и показательными картинками.
Рекомендую прочитать сначала краткую версию статьи, а затем при наличии желания и полную.
Краткая версия
Языки программирования по типизации принято делить на два больших лагеря — типизированные и нетипизированные (бестиповые). К первому например относятся C, Python, Scala, PHP и Lua, а ко второму — язык ассемблера, Forth и Brainfuck.
Так как «бестиповая типизация» по своей сути — проста как пробка, дальше она ни на какие другие виды не делится. А вот типизированные языки разделяются еще на несколько пересекающихся категорий:
Примеры:
Статическая: C, Java, C#;
Динамическая: Python, JavaScript, Ruby.
Примеры:
Сильная: Java, Python, Haskell, Lisp;
Слабая: C, JavaScript, Visual Basic, PHP.
Примеры:
Явная: C++, D, C#
Неявная: PHP, Lua, JavaScript
Также нужно заметить, что все эти категории пересекаются, например язык C имеет статическую слабую явную типизацию, а язык Python — динамическую сильную неявную.
Тем-не менее не бывает языков со статической и динамической типизаций одновременно. Хотя забегая вперед скажу, что тут я вру — они действительно существуют, но об этом позже.
Подробная версия
Если краткой версии Вам показалось недостаточно, хорошо. Не зря же я писал подробную? Главное, что в краткой версии просто невозможно было уместить всю полезную и интересную информацию, а подробная будет возможно слишком длинной, чтобы каждый смог ее прочесть, не напрягаясь.
Бестиповая типизация
В бестиповых языках программирования — все сущности считаются просто последовательностями бит, различной длины.
Бестиповая типизация обычно присуща низкоуровневым (язык ассемблера, Forth) и эзотерическим (Brainfuck, HQ9, Piet) языкам. Однако и у нее, наряду с недостатками, есть некоторые преимущества.
Преимущества
Недостатки
Сильная безтиповая типизация?
Да, такое существует. Например в языке ассемблера (для архитектуры х86/х86-64, других не знаю) нельзя ассемблировать программу, если вы попытаетесь загрузить в регистр cx (16 бит) данные из регистра rax (64 бита).
mov cx, eax ; ошибка времени ассемблирования
Так получается, что в ассемлере все-таки есть типизация? Я считаю, что этих проверок недостаточно. А Ваше мнение, конечно, зависит только от Вас.
Статическая и динамическая типизации
Главное, что отличает статическую (static) типизацию от динамической (dynamic) то, что все проверки типов выполняются на этапе компиляции, а не этапе выполнения.
Некоторым людям может показаться, что статическая типизация слишком ограничена (на самом деле так и есть, но от этого давно избавились с помощью некоторых методик). Некоторым же, что динамически типизированные языки — это игра с огнем, но какие же черты их выделяют? Неужели оба вида имеют шансы на существование? Если нет, то почему много как статически, так и динамически типизированных языков?
Преимущества статической типизации
Преимущества динамической типизации
Обобщенное программирование
Хорошо, самый важный аргумент за динамическую типизацию — удобство описания обобщенных алгоритмов. Давайте представим себе проблему — нам нужна функция поиска по нескольким массивам (или спискам) — по массиву целых чисел, по массиву вещественных и массиву символов.
Как же мы будем ее решать? Решим ее на 3-ех разных языках: одном с динамической типизацией и двух со статической.
Алгоритм поиска я возьму один из простейших — перебор. Функция будет получать искомый элемент, сам массив (или список) и возвращать индекс элемента, или, если элемент не найден — (-1).
Динамическое решение (Python):
Как видите, все просто и никаких проблем с тем, что список может содержать хоть числа, хоть списки, хоть другие массивы нет. Очень хорошо. Давайте пойдем дальше — решим эту-же задачу на Си!
Статическое решение (Си):
Ну, каждая функция в отдельности похожа на версию из Python, но почему их три? Неужели статическое программирование проиграло?
И да, и нет. Есть несколько методик программирования, одну из которых мы сейчас рассмотрим. Она называется обобщенное программирование и язык C++ ее неплохо поддерживает. Давайте посмотрим на новую версию:
Статическое решение (обобщенное программирование, C++):
Хорошо! Это выглядит не сильно сложнее чем версия на Python и при этом не пришлось много писать. Вдобавок мы получили реализацию для всех массивов, а не только для 3-ех, необходимых для решения задачи!
Эта версия похоже именно то, что нужно — мы получаем одновременно плюсы статической типизации и некоторые плюсы динамической.
Здорово, что это вообще возможно, но может быть еще лучше. Во-первых обобщенное программирование может быть удобнее и красивее (например в языке Haskell). Во-вторых помимо обобщенного программирования также можно применить полиморфизм (результат будет хуже), перегрузку функций (аналогично) или макросы.
Статика в динамике
Также нужно упомянуть, что многие статические языки позволяют использовать динамическую типизацию, например:
Сильная и слабая типизации
Языки с сильной типизацией не позволяют смешивать сущности разных типов в выражениях и не выполняют никаких автоматических преобразований. Также их называют «языки с строгой типизацией». Английский термин для этого — strong typing.
Слабо типизированные языки, наоборот всячески способствуют, чтобы программист смешивал разные типы в одном выражении, причем компилятор сам приведет все к единому типу. Также их называют «языки с нестрогой типизацией». Английский термин для этого — weak typing.
Слабую типизацию часто путают с динамической, что совершенно неверно. Динамически типизированный язык может быть и слабо и сильно типизирован.
Однако мало, кто придает значение строгости типизации. Часто заявляют, что если язык статически типизирован, то Вы сможете отловить множество потенциальных ошибок при компиляции. Они Вам врут!
Язык при этом должен иметь еще и сильную типизацию. И правда, если компилятор вместо сообщения об ошибке будет просто прибавлять строку к числу, или что еще хуже, вычтет из одного массива другой, какой нам толк, что все «проверки» типов будут на этапе компиляции? Правильно — слабая статическая типизация еще хуже, чем сильная динамическая! (Ну, это мое мнение)
Так что-же у слабой типизации вообще нет плюсов? Возможно так выглядит, однако несмотря на то, что я ярый сторонник сильной типизации, должен согласиться, что у слабой тоже есть преимущества.
Хотите узнать какие?
Преимущества сильной типизации
Преимущества слабой типизации
Оказывается есть и даже два.
Неявное приведение типов, в однозначных ситуациях и без потерь данных
Ух… Довольно длинный пункт. Давайте я буду дальше сокращать его до «ограниченное неявное преобразование» Так что же значит однозначная ситуация и потери данных?
Однозначная ситуация, это преобразование или операция в которой сущность сразу понятна. Вот например сложение двух чисел — однозначная ситуация. А преобразование числа в массив — нет (возможно создастся массив из одного элемента, возможно массив, с такой длинной, заполненный элементами по-умолчанию, а возможно число преобразуется в строку, а затем в массив символов).
Потеря данных это еще проще. Если мы преобразуем вещественное число 3.5 в целое — мы потеряем часть данных (на самом деле эта операция еще и неоднозначная — как будет производиться округление? В большую сторону? В меньшую? Отбрасывание дробной части?).
Преобразования в неоднозначных ситуациях и преобразования с потерей данных — это очень, очень плохо. Ничего хуже этого в программировании нет.
Если вы мне не верите, изучите язык PL/I или даже просто поищите его спецификацию. В нем есть правила преобразования между ВСЕМИ типами данных! Это просто ад!
Ладно, давайте вспомним про ограниченное неявное преобразование. Есть ли такие языки? Да, например в Pascal Вы можете преобразовать целое число в вещественное, но не наоборот. Также похожие механизмы есть в C#, Groovy и Common Lisp.
Ладно, я говорил, что есть еще способ получить пару плюсов слабой типизации в сильном языке. И да, он есть и называется полиморфизм конструкторов.
Я поясню его на примере замечательного языка Haskell.
Полиморфные конструкторы появились в результате наблюдения, что чаще всего безопасные неявные преобразования нужны при использовании числовых литералов.
И это сделано в Haskell, благодаря тому, что у литерала 1 нет конкретного типа. Это ни целое, ни вещественное, ни комплексное. Это же просто число!
Конечно спасает этот прием только при использовании смешанных выражений с числовыми литералами, а это лишь верхушка айсберга.
Таким образом можно сказать, что лучшим выходом будет балансирование на грани, между сильной и слабой типизацией. Но пока идеальный баланс не держит ни один язык, поэтому я больше склоняюсь к сильно типизированным языкам (таким как Haskell, Java, C#, Python), а не к слабо типизированным (таким как C, JavaScript, Lua, PHP).
Ладно, пойдем дальше?
Явная и неявная типизации
Язык с явной типизацией предполагает, что программист должен указывать типы всех переменных и функций, которые объявляет. Английский термин для этого — explicit typing.
Язык с неявной типизацией, напротив, предлагает Вам забыть о типах и переложить задачу вывода типов на компилятор или интерпретатор. Английски термин для этого — implicit typing.
По-началу можно решить, что неявная типизация равносильна динамической, а явная — статической, но дальше мы увидим, что это не так.
Есть ли плюсы у каждого вида, и опять же, есть ли их комбинации и есть ли языки с поддержкой обоих методов?
Преимущества явной типизации
Преимущества неявной типизации
Явная типизация по-выбору
Есть языки, с неявной типизацией по-умолчанию и возможностью указать тип значений при необходимости. Настоящий тип выражения транслятор выведет автоматически. Один из таких языков — Haskell, давайте я приведу простой пример, для наглядности:
* Спасибо int_index за нахождение ошибки.
Хм. Как мы видим, это очень красиво и коротко. Запись функции занимает всего 18 символов на одной строчке, включая пробелы!
Однако автоматический вывод типов довольно сложная вещь, и даже в таком крутом языке как Haskell, он иногда не справляется. (как пример можно привести ограничение мономорфизма)
Есть ли языки с явной типизацией по-умолчанию и неявной по-необходимости? Кон
ечно.
Неявная типизация по-выбору
В новом стандарте языка C++, названном C++11 (ранее назывался C++0x), было введено ключевое слово auto, благодаря которому можно заставить компилятор вывести тип, исходя из контекста:
Неплохо. Но запись сократилась не сильно. Давайте посмотрим пример с итераторами (если не понимаете, не бойтесь, главное заметьте, что запись благодаря автоматическому выводу очень сильно сокращается):
Ух ты! Вот это сокращение. Ладно, но можно ли сделать что-нибудь в духе Haskell, где тип возвращаемого значения будет зависеть от типов аргументов?
И опять ответ да, благодаря ключевому слову decltype в комбинации с auto:
Может показаться, что эта форма записи не сильно хороша, но в комбинации с обобщенным программированием (templates / generics) неявная типизация или автоматический вывод типов творят чудеса.
Некоторые языки программирования по данной классификации
Я приведу небольшой список из популярных языков и напишу как они подразделяются по каждой категории “типизаций”.
Возможно я где-то ошибся, особенно с CL, PHP и Obj-C, если по какому-то языку у Вас другое мнение — напишите в комментариях.
Заключение
Окей. Уже скоро будет светло и я чувствую, что про типизацию больше нечего сказать. Ой как? Тема бездонная? Очень много осталось недосказано? Прошу в комментарии, поделитесь полезной информацией.
Основные принципы программирования: статическая и динамическая типизация
Авторизуйтесь
Основные принципы программирования: статическая и динамическая типизация
Когда вы изучаете языки программирования, то в разговорах часто слышите фразы наподобие «статически типизированный» или «динамически типизированный». Эти понятия описывают процесс проверки соответствия типов, и как статическая проверка типов, так и динамическая, относятся к разным системам типов. Система типов — это набор правил, которые присваивают свойство, называющееся «тип», различным сущностям в программе: переменным, выражениям, функциям или модулями — с конечной целью уменьшения количества ошибок путём подтверждения того, что данные отображаются корректно.
Не волнуйтесь, я знаю, что это всё звучит запутанно, поэтому мы начнём с основ. Что такое «проверка соответствия типов» и что такое вообще тип?
Тип, также известный как тип данных, это вид классификации, отмечающий одних из различных видов данных. Я не люблю использовать слово «тип» в этом смысле, поэтому скажем так: тип описывает возможные значения структуры (например, переменной), её семантическое значение и способ хранения в памяти. Если это звучит непонятно, подумайте о целых, строках, числах с плавающей запятой и булевых величинах — это всё типы. Типы можно разбить на категории:
В различных языках могут различаться примитивные типы и структуры данных, но принцип везде один. Тип просто определяет набор правил и протоколов, которым должен соответствовать элемент данных.
Проверка соответствия типов
Существование типов не имеет смысла без процесса подтверждения того, что эти типы имеют логический смысл и программа может быть корректно исполнена. Тут и приходит на помощь проверка соответствия типов. Это процесс подтверждения и применения ограничений типов, и он может проходить как во время компиляции (т.е. статически), так и во время выполнения (т.е. динамически). Проверка соответствия типов нужна, чтобы убедиться в типобезопасности программы, что сведёт количество ошибок, связанных с типами, к минимуму. Это такие ошибки, которые возникают, когда операция производится с тем типом, с каким она производиться не должна: например, когда целое число принимается за число с плавающей запятой, или когда происходит сложения строки и целого:
Хотя во многих языках и строки, и числа могут использовать оператор +, это зачастую приводит к ошибкам типа, поскольку это выражение обычно не работает с разными типами данных.
Для случая не типобезопасной программы единого алгоритма действий нет. Какие-то языки выдают ошибку типа, которая останавливает компиляцию или выполнение, а в каких-то есть встроенные обработчики таких ошибок (что позволяет разработчикам порой ошибаться при работе с типами). Вне зависимости от этого, процесс проверки типов — это необходимость.
Теперь, когда мы знаем, что такое типы и как работает проверка соответствия типов, рассмотрим два основных вида проверки: статическую и динамическую.
Статическая проверка типов
Язык обладает статической типизацией, если тип переменной известен во время компиляции, а не выполнения. Типичными примерами таких языков являются Ada, C, C++, C#, JADE, Java, Fortran, Haskell, ML, Pascal, и Scala.
Большим преимуществом статической проверки типов является тот факт, что большую часть ошибок типов можно отловить на ранней стадии разработки. Статическая типизация обычно приводит к более быстрому исполнению скомпилированного кода, потому что компилятор знает точные типы используемых данных и создаёт оптимизированный машинный код. Статическая проверка типов оценивает лишь информацию, доступную во время компиляции, а также может подтвердить, что проверенные условия соблюдаются для всех возможных вариантов исполнения программы, что избавляет от необходимости проверки перед каждым запуском программы. Без статической проверки типов даже 100%-ное покрытие тестами не всегда поможет выявить некоторые ошибки типизации.
Динамическая проверка типов
Динамическая проверка типов — это процесс подтверждения типобезопасности программы во время её выполнения. Типичными примерами динамически типизированных языков являются Groovy, JavaScript, Lisp, Lua, Objective-C, PHP, Prolog, Python, Ruby, Smalltalk и Tcl.
Большая часть типобезопасных языков в той или иной мере использует динамическую проверку типов, даже если основным инструментом является статическая. Так происходит из-за того, что многие свойства невозможно проверить статически. Предположим, что программа определяет два типа, A и B, где B — подтип A. Если программа пытается преобразовать тип A в тип B, т.е. произвести понижающее приведение, то эта операция будет одобрена лишь в том случае, когда значение на самом деле имеет тип B. Поэтому для подтверждения безопасности операции нужна динамическая проверка типов.
В отличие от статической проверки типов, динамическая может привести к прекращению выполнения программы из-за ошибок типизации. В некоторых языках этого может избежать (например, благодаря обработке ошибок или слабой типобезопасности). Для избежания подобных ситуации рекомендуется использовать юнит-тесты.
Код, прошедший динамическую проверку типов, в общем случае менее оптимизирован; кроме того, существует возможность ошибок выполнения и, как следствие, необходимость проверки перед каждым запуском. Тем не менее, динамическая типизация открывает дорогу другим, мощным техникам программирования, например, метапрограммированию.
Типичные заблуждения
Миф 1: статическая / динамическая типизация == сильная / слабая типизация
Обычным заблуждение является мнение, что все статически типизированные языки являются сильно типизированными, а динамически типизированные — слабо типизированными. Это неверно, и вот почему.
Сильно типизированный язык — это такой язык, в котором переменные привязаны к конкретным типам данных, и который выдаст ошибку типизации в случае несовпадения ожидаемого и фактического типов — когда бы не проводилась проверка. Проще всего представить сильно типизированный язык как язык с высокой типобезопасностью. Например, в уже использованном выше куске кода сильно типизированный язык выдаст явную ошибку типизации, которая прервёт выполнение программы:
Мы часто ассоциируем статически типизированные языки, такие как Java и C#, с сильно типизированным (они такими и являются), поскольку тип данных задаётся явно при инициализации переменной — как в этом примере на Java:
Тем не менее, Ruby, Python и JavaScript (все они обладaют динамической типизацией) также являются сильно типизированными, хотя разработчику и не нужно указывать тип переменной при объявлении. Рассмотрим такой же пример, но написанный на Ruby:
Оба языка являются сильно типизированными, но используют разные методы проверки типов. Такие языки, как Ruby, Python и JavaScript не требуют явного определения типов из-за вывода типов — способности программно выводить нужный тип переменной в зависимости от её значения. Вывод типов — это отдельное свойство языка, и не относится к системам типов.
Слабо типизированный язык — это язык, в котором переменные не привязаны к конкретному типу данных; у них всё ещё есть тип, но ограничения типобезопасности гораздо слабее. Рассмотрим следующий пример кода на PHP:
Поскольку PHP обладает слабой типизацией, ошибки в этом коде нет. Аналогично предыдущему предположению, не все слабо типизированные языки являются динамически типизированными: PHP — это динамически типизированный язык, но вот C — тоже язык со слабой типизацией — воистину статически типизирован.
Хотя статическая / динамическая и сильная / слабая системы типов и являются разными, они обе связаны с типобезопасностью. Проще всего это выразить так: первая система говорит о том, когда проверяется типобезопасность, а вторая — как.
Миф 2: статическая / динамическая типизация == компилируемые / интерпретируемые языки
Будет верным сказать, что большинство статически типизированных языков обычно компилируются, а динамически типизированных — интерпретируются, но обобщить это утверждение нельзя, и тому есть простой пример.
Когда мы говорим о типизации языка, мы говорим о языке как о целом. Например, неважно, какую версию Java вы используете — она всегда будет статически типизированной. Это отличается от того случая, когда язык является компилируемым или интерпретируемым, поскольку в этом случае мы говорим о конкретной реализации языка. В теории, любой язык может быть как компилируемым, так и интерпретируемым. Самая популярная реализация языка Java использует компиляцию в байткод, который интерпретирует JVM — но есть и иные реализации этого языка, которые компилируются напрямую в машинный код или интерпретируются как есть.
Если это всё ещё непонятно, советую прочесть одну из предыдущих статей этого цикла.
Заключение
Я знаю, что в этой статье было много информации — но я верю, что вы справились. Я бы хотел вынести информацию про сильную / слабую типизацию в отдельную статью, но это не такая важная тема; к тому же, нужно было показать, что этот вид типизации не имеет отношения к проверке типов.
Нет однозначного ответа на вопрос «какая типизация лучше?» — у каждой есть свои преимущества и недостатки. Некоторые языки — такие как Perl и C# — даже позволяют вам самостоятельно выбирать между статической и динамической системами проверки типов. Понимание этих систем позволит вам лучше понять природу возникающих ошибок, а также упростит борьбу с ними.
Часто задаваемые вопросы о системах типов
Автор статьи, перевод которой мы сегодня публикуем, говорит, что источником вдохновения для её написания послужил этот пост и комментарии к нему. По его словам, IT-специалисты имеют неправильные представления о типах, используют некорректную терминологию и, обсуждая вопросы, связанные с типами, приходят к ошибочным выводам. Он отмечает то, что не является защитником статической системы типов. Единственное, что его беспокоит — это правильное использование терминов. Это позволяет вести конструктивные дискуссии. Автор говорит, что написал этот материал спонтанно, но надеется на то, что в нём нет ошибок. Если же он что-то и напутал — он просит дать ему об этом знать.
Давайте раз и навсегда разберёмся во всём том, что вызывает неразбериху при разговорах о системах типов.
Динамическая типизация и отсутствие типизации
Некоторые люди считают, что динамическая система типов (dynamic type system) — это то же самое, что и система типов с отсутствием типизации (untyped type system). Отсутствие типизации означает, что в некоей системе типов нет смысла различать типы. Нет смысла различать типы и в том случае, если в системе типов присутствует всего один тип. Например:
Языки, которые не ограничивают область значений переменных, называются нетипизированными языками: в них нет типов, или, что одно и то же, в них есть лишь один универсальный тип, который содержит все значения.
«Системы типов», Лука Карделли
Языки программирования обладают одним интересным признаком, который позволяет грубо разделить их мир на две группы:
Динамическая и статическая типизация
Динамическая система типов (dynamic type system) — это такая система, в которой типы проверяются динамически (во время исполнения программы). Статическая система типов (static type system) — это система, в которой типы проверяются статически (во время компиляции или транспиляции кода).
Является ли одна из этих систем противоположностью другой? Нет, не является. В одном и том же языке могут применяться обе эти системы типов. На самом деле, в большинстве статических систем типов имеются и динамические проверки типов. В качестве примера можно рассмотреть валидацию операций ввода-вывода (input-output, IO). Представьте себе, что вам нужно прочитать данные, предоставленные пользователем, который должен ввести число. Вы будете проверять, во время исполнения программы, является ли число результатом разбора соответствующей строки (в результате разбора может быть выдано исключение или возвращено нечто вроде NaN ). Когда вы проверяете введённые пользователем данные, выясняя, могут ли они рассматриваться как число — вы выполняете динамическую проверку типа.
В результате можно отметить отсутствие противостояния статических и динамических типов. Можно пользоваться, в одном и том же языке, и теми и другими.
Более того, надо отметить, что статическая проверка типов — это сложный процесс. Иногда бывает очень непросто статически проверить некоторые части программы. В результате вместо применения статических проверок типов можно прибегнуть к динамическим проверкам.
Рекомендуется рассматривать статическую систему типов как типы, проверяемые статически. А динамическую систему типов — как типы, проверяемые динамически.
Означает ли применение статических типов знание типов во время компиляции программы?
На вопрос, вынесенный в заголовок этого раздела, можно ответить отрицательно. Если открыть исходный код любого парсера (включая парсер JavaScript) — можно увидеть, что парсеру известны типы значений во время парсинга (это — часть процесса компиляции кода).
Оказывается, что парсер знает о том, что «test» — это строка. Делает ли это JavaScript языком со статической типизацией? Нет, не делает.
Постепенная типизация
С одной стороны, это делает систему типов менее безопасной. С другой — система типов с постепенной типизацией позволяет постепенно добавлять описания типов в языки с динамической типизацией.
Надёжные и ненадёжные системы типов
При использовании надёжной системы типов (sound type system) программа, в ходе проверки типов, не будет «одобрена» в том случае, если в этой программе есть ошибки, связанные с типами. Применение ненадёжной системы типов (unsound type system) приводит к тому, что в программе могут присутствовать ошибки, связанные с типами. Не следует, правда, впадать в панику после того, как вы об этом узнали. На практике это может вас не коснуться. Надёжность или корректность (soundness) — это математическое свойство алгоритма проверки типов, которое нуждается в доказательстве. Множество существующих компиляторов (внутри это — системы проверки типов) ненадёжны.
Если вы хотите работать с надёжными системами типов — взгляните на языки программирования семейства ML, в которых используется система типов Хиндли-Милнера (Hindley-Milner).
Кроме того, надо понимать, что надёжная система типов не пропустит неправильную программу (не даёт ложноположительных результатов проверок, считая неправильные программы правильными), но может не пропустить и правильную программу (способна дать ложноотрицательные результаты проверок).
Система типов, которая никогда не отвергает правильные программы, называется полной (complete).
Бывает ли так, что система типов является и надёжной, и полной? Насколько я знаю — таких систем типов не существует. До конца я в этом не уверен, но мне кажется, что существование таких систем типов, если основываться на теореме Гёделя о неполноте, фундаментально невозможно (в этом я, правда, могу и ошибаться).
Слабая и сильная типизация
Я считаю нецелесообразным использование терминов «слабая типизация» (weak typing) и «сильная типизация» (strong typing). Эти термины неоднозначны, их применение может дать больше путаницы, чем ясности. Приведу несколько цитат.
Эти языки могут называться, образно говоря, языками со слабой проверкой типов (или слабо типизированными языками, как их обычно называют в различных публикациях). Использование в языке слабой проверки типов означает, что некоторые небезопасные операции выявляются статически, а некоторые — нет. «Слабость» проверок типов в языках этого класса серьёзно варьируется.
«Системы типов», Лука Карделли
Вероятно, самым распространённым способом классификации систем типов является их разделение на системы со «слабой» и «сильной» типизацией. Об этом можно лишь сожалеть, так как данные слова не несут в себе практически никакого смысла. Можно, в ограниченных пределах, сравнивать два языка, имеющих очень похожие системы типов, и выделять один из них, как имеющий более сильную, чем второй, систему типов. В других случаях термины «сильная типизация» и «слабая типизация» совершенно не имеют смысла.
«Что надо знать до начала обсуждения систем типов», Стив Клабник
Термины «сильная типизация» и «слабая типизация» чрезвычайно неоднозначны. Вот несколько примеров их использования:
Нуждаются ли языки со статической типизацией в объявлениях типов?
Языки со статической типизацией нуждаются в объявлениях типов не всегда. Иногда система типов может вывести типы (выдвигая предположения, основанные на структуре кода). Вот пример (TypeScript):
Система типов знает о том, что «test» — это строка (это знание основано на правилах парсинга кода). Система типов знает и о том, что x — это константа, то есть — значение x нельзя переназначить. В результате может быть сделан вывод о том, что x имеет строковой тип.
Вот ещё один пример (Flow):
Здесь нет объявлений типов, но это не мешает провести статическую проверку типов представленной выше программы. Если вы столкнётесь с подобными ситуациями в реальном мире, то вам, рано или поздно, придётся объявлять некоторые типы. Система типов не может вывести абсолютно все типы. Но нужно понимать, что язык может называться «статическим» не из-за того, что в нём используются объявления типов, а из-за того, что типы проверяются до запуска программы.
Является ли TypeScript небезопасным языком из-за того, что код, написанный на нём, компилируется в JavaScript-код?
TypeScript — ненадёжный (unsound) язык. Поэтому код, написанный на нём, может превращаться в небезопасные приложения. Но это не имеет никакого отношения к тому, во что он компилируется.
Большинство компиляторов для настольных систем преобразуют программы в нечто, напоминающее язык ассемблера. А ассемблер — это язык, который отличается ещё меньшей безопасностью, чем JS.
Тут, если вернуться к мысли о небезопасности TS из-за компиляции в JS, у вас может появиться следующая мысль: «Скомпилированный код выполняется в браузере, JS — язык небезопасный, и в то место, где ожидается строка, он вполне может подставить значение null ». Мысль это дельная. Но это, опять же, не даёт повода называть TS небезопасным языком. Для того чтобы TS мог гарантировать безопасность внутри приложения, вам нужно разместить «защитные механизмы» в тех местах, где TS-код взаимодействует с внешним миром. То есть, например, нужно проверять корректность данных, поступающих в программу через механизмы ввода-вывода. Скажем, это может быть проверка того, что вводит пользователь, проверка ответов сервера, проверка данных, читаемых из хранилища браузера и так далее.
Например, роль подобных «защитных механизмов» в Elm играют «порты». В TS для этого можно использовать нечто вроде io-ts.
Соответствующий «защитный механизм» создаёт мост между статической и динамической системами типов.
Вот упрощённый пример:
Правда ли то, что типы нужны лишь для компиляторов?
Типы — это всего лишь хак, нужный для того, чтобы давать подсказки компилятору.
Ваут Мертенс
Типы нужны лишь компиляторам? Это — философский вопрос. Типы необходимы для людей, а не для машин. Компиляторы нуждаются в типах из-за того, что они являются программами, написанными людьми.
Феномен типов существует из-за людей. Типов не существует до тех пор, пока человек не воспринимает нечто в виде «типа данных». Человеческий разум распределяет разные сущности по разным категориям. Типы не имеют смысла без наблюдателя.
Давайте устроим мысленный эксперимент. Подумайте об игре «Жизнь». У вас имеется двумерная сетка, состоящая из квадратных ячеек. Каждая из ячеек может пребывать в двух возможных состояниях. Она может быть «живой» или «мёртвой». Каждая ячейка может взаимодействовать со своими восемью соседями. Это — ячейки, которые граничат с ней по вертикали, по горизонтали, или по диагонали. В процессе нахождения очередного состояния системы применяются следующие правила:
Если какое-то время понаблюдать за «Жизнью», то на поле могут появиться устойчивые структуры наподобие «планера» («glider»).
Видите его? По экрану движется «планер». Правда? А теперь давайте немного притормозим. Существует ли этот «планер» на самом деле? Это — просто отдельные квадраты, которые появляются и исчезают. Но наш мозг может воспринимать эту структуру как нечто, объективно существующее.
Мы, кроме того, можем сказать, что «планер» существует из-за того, что квадраты не являются независимыми (они зависят от соседей), и даже если сам по себе «планер» и не существует, то существует «планер» в виде платонической идеи.
Итоги
Поразмыслите о любой программе, написанной на типизированном языке программирования. Мы можем наблюдать типы. Верно? Но программа компилируется в машинные коды. В этих кодах выражено то же самое, что и в исходной программе (правда, человеку тяжело читать машинные представления программ). С точки зрения компьютера здесь нет типов. Он видит лишь последовательности битов — наборы из нулей и единиц («мёртвые» и «живые» ячейки). Типы существуют для людей, а не для машин.
Уважаемые читатели! Как вы думаете, какая система типов могла бы считаться идеальной для целей веб-разработки?




