Статическая и строгая типизация. Ликбез по типизации в языках программирования. Языки с динамической типизацией

💖 Нравится? Поделись с друзьями ссылкой

Чтобы максимально просто объяснить две абсолютно разные технологии, начнём сначала. Первое, с чем сталкивается программист при написании кода - объявление переменных. Вы можете заметить, что, например, в языке программирования C++ необходимо указывать тип переменной. То есть если вы объявляете переменную x, то обязательно нужно добавить int - для хранения целочисленных данных, float - для хранения данных с плавающей точкой, char - для символьных данных, и другие доступные типы. Следовательно, в C++ используется статическая типизация, так же как и в его предшественнике C.

Как работает статическая типизация?

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

Рассмотрим небольшой пример. При инициализации переменной x (int x;) мы указываем идентификатор int - это сокращение от который хранит только целые числа в диапазоне от - 2 147 483 648 до 2 147 483 647. Таким образом, компилятор понимает, что может выполнять над этой переменной математические значения - сумму, разность, умножение и деление. А вот, например, функцию strcat(), которая соединяет два значения типа char, применить к x нельзя. Ведь если снять ограничения и попробовать соединить два значения int символьным методом, тогда произойдет ошибка.

Зачем понадобились языки с динамической типизацией?

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

Удачный пример, который можно привести - JavaScript. Этот язык программирования обычно используют для встраивания в фреймворк с целью получения функционального доступа к объектам. Из-за такой особенности он приобрел большую популярность в web-технологиях, где идеально чувствует себя динамичная типизация. В разы упрощается написание небольших скриптов и макросов. А также появляется преимущество в повторном использовании переменных. Но такую возможность используют довольно редко, из-за возможных путаниц и ошибок.

Какой вид типизации лучше?

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

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

Разделение на «сильную» и «слабую» типизацию

Среди как русскоязычных, так и англоязычных материалов по программированию можно встретить выражение - «сильная» типизация. Это не отдельное понятие, а точнее такого понятия в профессиональном лексиконе вообще не существует. Хотя многие пытаются его по-разному интерпретировать. На самом деле, «сильную» типизацию следует понимать как ту, которая удобна именно для вас и с которой максимально комфортно работать. А «слабая» - неудобная и неэффективная для вас система.

Особенность динамики

Наверняка вы замечали, что на стадии написании кода компилятор анализирует написанные конструкции и выдаст ошибку при несовпадении типов данных. Но только не JavaScript. Его уникальность в том, что он в любом случае произведет операцию. Вот легкий пример - мы хотим сложить символ и число, что не имеет смысла: «x» + 1.

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

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

Возможны ли смежные архитектуры?

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

Но, тем не менее, в некоторых языках можно поменять типизацию с помощью дополнительных фреймворков.

  • В языке программирования Delphi - подсистема Variant.
  • В языке программирования AliceML - дополнительные пакеты.
  • В языке программирования Haskell - библиотека Data.Dynamic.

Когда строгая типизация действительно лучше динамической?

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

Преимущества динамической типизации

  • Сводит к минимуму количество символов и строк кода из-за ненадобности предварительного объявления переменных и указания их типа. Тип будет определен автоматически, после присвоения значения.
  • В небольших блоках кода упрощается визуальное и логическое восприятие конструкций, из-за отсутствия «лишних» строк объявления.
  • Динамика положительно влияет на скорость работы компилятора, так как он не учитывает типы, и не проверяет их на соответствие.
  • Повышает гибкость и позволяет создавать универсальные конструкции. К примеру, при создании метода, который должен взаимодействовать с массивом данных, не нужно создавать отдельные функции для работы с числовыми, текстовыми и другими типами массивов. Достаточно написать один метод, и он будет работать с любыми типами.
  • Упрощает вывод данных из систем управления базами данных, поэтому динамическую типизацию активно используют при разработке веб-приложений.

Подробнее о языках программирования со статической типизацией

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

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

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

Подробнее о языках программирования с динамическим видом типизации

  • Python - язык программирования, который создавался прежде всего для облегчения работы программиста. Имеет ряд функциональных улучшений, благодаря которым увеличивает читабельность кода и его написание. Во многом этого удалось добиться благодаря динамической типизации.

  • PHP - язык для создания скриптов. Повсеместно применяется в веб-разработке, обеспечивая взаимодействие с базами данных, для создания интерактивных динамических веб-страниц. Благодаря динамической типизации существенно облегчается работы с базами данных.

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

Динамический вид типизации - недостатки

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

Подведем итог

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

Дело в том, что отсутствие типов убивает сразу много зайцев. Главные - это читаемость кода, и повышение простоты программирования в целом. Первое - это очень важно стратегически, чтобы при большом объёме кода, долгой разработке проект по прежнему был читаем и расширяем, второе - тактически - чтобы быстро решать задачи. Но приверженец строгой типизации может не почувствовать этих плюсов на первых порах, например я сам перешёл когда-то с C++ на JS и PHP - и наверное ещё год негодовал. Постараюсь объяснить..

Ресурсы мозга . Присутствие типов в коде создаёт дополнительную неслабую смысловую нагрузку, и требует серьёзных мозговых затрат на запоминание иерархии, на чтение кода, на то чтобы эти самые типы прописывать при каждом "чихе" в коде. В компилируемом коде типы раньше несли за собой главный их смысл - максимальную скорость работы программы, минимальный расход ОП. Для скриптовых языков такой цели как правило не стоит (да и современные компиляторы уже не слишком много отъедают при отсутствии типов), и поэтому решающие факторы типизации языка - именно восприятие типов мозгами программистов в контексте задач скриптового языка.

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

Читаемость . Если грамотно именовать, переменные в коде JS - то код будет читаться как книга, несмотря на структурную сложность. Но когда к переменным добавляются типы - читаемость сильно теряет: читающий программист получает много лишней информации. Вот в быту мы говорим Убери яблоко в холодильник а не Убери красное круглое яблоко в большой серебристый холодильник, так как первое проще воспринимается. Например, чтение JS кода любой широко-распостраннённой библиотеки я бы сравнил с чтением стихов Пушкина, тогда как чтение типизированного C++ кода с чтением Иллиады Гомера (теперь уже).

Про простоту программирования без типов . Выходит так, что без типов, и с развитыми функциональными инструментами: половина паттернов не нужна, вторая половина паттернов записывается в три строчки вместо 15-ти. Классическая реализация старых паттернов GoF в типизированных языках сегодня больше похожа на костыли, которые просто есть, чтобы компенсировать недостаток гибкости при наличии строгой типизации - соответственно они нечитаемы, с ними надо серьёзнее разбираться, забивать себе голову вопросами "зачем это?" "это фасад или адаптер?", и.т.п. В JS же появляются плюс к старым совсем другие подходы, основанные на замыканиях, инкапсуляции, и ФП. При реализации одной-и-той же задачи в стиле ООП-классики на TS, и в стиле ФП на JS, что выходит в итоге:

  1. Код на JS получается компактнее раза в 3, и читаемее, чем код со строгой типизацией на том-же TS.
  2. Программист реализует задачу быстрее, пропорционально объёму кода.

Плюс JS работает в основном с древовидной базой данных под названием DOM - и его задача, это в основном управление этим DOM в динамике. То есть никаких серьёзных ООП структур не требуется, встроенного хватает с лихвой: обработки DOM событий, асинхронных возможностей JS. Это вкупе с отсутсвием типизации делает JS-код "квинтэссенцией управляющей логики". "Простота программирования" - это не значит, что у программистов JS будет халява, это значит что хороший программист успеет сделать в 3 раза больше.

Что теряет JS без типов?
Первое, что теряет JS - автоматическую валидацию входящих параметров функции - это почти главная роль которую исполняют типы в современных скриптовых языках. Параметры можно валидировать вручную в JS, но это делают крайне редко, разве что часто бывают проверки на пустоту вида if (!param1) return false; . Почему не делают? Да потому что JS проносит в себе стиль кода, в котором тип - это не главная характиристика переменной - и программисты быстро это понимают.
Второе что теряет JS - отсутствие возможности строить классическую ООП архитектуру. Нет, костылями и велосипедами конечно можно делать сильное подобие ООП - когда-то (далеко до ES6) этим вопросом задались создатели mootools, и сегодня 90% программистов JS, сталкивающихся с mootools считают его худшим фреймворком всех времён и народов) Почему? Да потому что в JS богатейший функционал, в том числе касаемо инкапсуляции(так необходимой для крупных проектов), множество способов делать "безопасные" для программы точки расширения, множество "своих паттернов", ООП ему не не нужен, чтобы сделать качественную расширяемую программу любого масштаба. Типизация, тем более строгая - есть локомотив "дедовского ООП" - и соответственно угнетатель гибких способов построения программ.
Последнее - это при работе в IDE автокомплит и "переход к классу" работают плохо. Это бывает, но если вы всё-таки решили писать ООП-подобный код в js, то воспользуйтесь JSDoc , чтобы помочь IDE. В то-же время плохая навигация, это проблема самого IDE, например PhpStorm(WebStorm) - работает с навигацией в JS хорошо и без JSDoc, и обещает, что скоро станет отлично.

Описал своими словами, как мог, но сложно объяснить множество плюсов отсутствия типов программисту на строго-типизированных языках: это почти что донести буддисту, что христианство это круто:)

UPD но дабы не превращать ответ в злостный холивар, надо заметить - что типы тоже полезны для той-же читаемости кода, например в аргументах функций. Но когда типы используются иногда, а не в 100% кода. Но если разрешить в JS пусть только типы в аргументах (например как в TS), программисты тут-же начнут злоупотреблять ООП, и убьют сформировавшуюся стилистику языка, и вместе с этим все плюсы JS - думаю это причина идеологии типизации EcmaScript. Бесполезна и даже вредна именно строгая типизация, но только если речь не идёт о максимальной производительности: тогда нет вопросов, строгая типизация выйграет.

  • Динами́ческая типиза́ция - приём, широко используемый в языках программирования и языках спецификации, при котором переменная связывается с типом в момент присваивания значения, а не в момент объявления переменной. Таким образом, в различных участках программы одна и та же переменная может принимать значения разных типов. Примеры языков с динамической типизацией - Smalltalk, Python, Objective-C, Ruby, PHP, Perl, JavaScript, Lisp, xBase, Erlang, Visual Basic.

    Противоположный приём - статическая типизация.

    В некоторых языках со слабой динамической типизацией стоит проблема сравнения величин, так, например, PHP имеет операции сравнения «==», «!=» и «===», «!==», где вторая пара операций сравнивает и значения, и типы переменных. Операция «===» даёт true только при полном совпадении, в отличие от «==», который считает верным такое выражение: (1=="1"). Стоит отметить, что это проблема не динамической типизации в целом, а конкретных языков программирования.

Связанные понятия

Язык программи́рования - формальный язык, предназначенный для записи компьютерных программ. Язык программирования определяет набор лексических, синтаксических и семантических правил, определяющих внешний вид программы и действия, которые выполнит исполнитель (обычно - ЭВМ) под её управлением.

Синтаксический сахар (англ. syntactic sugar) в языке программирования - это синтаксические возможности, применение которых не влияет на поведение программы, но делает использование языка более удобным для человека.

Свойство - способ доступа к внутреннему состоянию объекта, имитирующий переменную некоторого типа. Обращение к свойству объекта выглядит так же, как и обращение к структурному полю (в структурном программировании), но, в действительности, реализовано через вызов функции. При попытке задать значение данного свойства вызывается один метод, а при попытке получить значение данного свойства - другой.

Расширенная форма Бэкуса - Наура (расширенная Бэкус - Наурова форма (РБНФ)) (англ. Extended Backus–Naur Form (EBNF)) - формальная система определения синтаксиса, в которой одни синтаксические категории последовательно определяются через другие. Используется для описания контекстно-свободных формальных грамматик. Предложена Никлаусом Виртом. Является расширенной переработкой форм Бэкуса - Наура, отличается от БНФ более «ёмкими» конструкциями, позволяющими при той же выразительной способности упростить...

Аппликативное программирование - один из видов декларативного программирования, в котором написание программы состоит в систематическом осуществлении применения одного объекта к другому. Результатом такого применения вновь является объект, который может участвовать в применениях как в роли функции, так и в роли аргумента и так далее. Это делает запись программы математически ясной. Тот факт, что функция обозначается выражением, свидетельствует о возможности использования значений-функций - функциональных...

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

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

Синтакси́ческий ана́лиз (или разбор, жарг. па́рсинг ← англ. parsing) в лингвистике и информатике - процесс сопоставления линейной последовательности лексем (слов, токенов) естественного или формального языка с его формальной грамматикой. Результатом обычно является дерево разбора (синтаксическое дерево). Обычно применяется совместно с лексическим анализом.

Обобщённый алгебраический тип да́нных (англ. generalized algebraic data type, GADT) - один из видов алгебраических типов данных, который характеризуется тем, что его конструкторы могут возвращать значения не своего типа, связанного с ним. Сконструированы под влиянием работ об индуктивных семействах в среде исследователей зависимых типов.

Сема́нтика в программировании - дисциплина, изучающая формализации значений конструкций языков программирования посредством построения их формальных математических моделей. В качестве инструментов построения таких моделей могут использоваться различные средства, например, математическая логика, λ-исчисление, теория множеств, теория категорий, теория моделей, универсальная алгебра. Формализация семантики языка программирования может использоваться как для описания языка, определения свойств языка...

Объе́ктно-ориенти́рованное программи́рование (ООП) - методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определённого класса, а классы образуют иерархию наследования.

Динами́ческая переме́нная - переменная в программе, место в оперативной памяти под которую выделяется во время выполнения программы. По сути, она является участком памяти, выделенным системой программе для конкретных целей во время работы программы. Этим она отличается от глобальной статической переменной - участка памяти, выделенного системой программе для конкретных целей перед началом работы программы. Динамическая переменная - один из классов памяти переменной.

Когда вы изучаете языки программирования, то в разговорах часто слышите фразы наподобие “статически типизированный” или “динамически типизированный”. Эти понятия описывают процесс проверки соответствия типов, и как статическая проверка типов, так и динамическая, относятся к разным системам типов. Система типов - это набор правил, которые присваивают свойство, называющееся “тип”, различным сущностям в программе: переменным, выражениям, функциям или модулями - с конечной целью уменьшения количества ошибок путём подтверждения того, что данные отображаются корректно.

Не волнуйтесь, я знаю, что это всё звучит запутанно, поэтому мы начнём с основ. Что такое “проверка соответствия типов” и что такое вообще тип?

Тип

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

Типичные заблуждения

Миф 1: статическая / динамическая типизация == сильная / слабая типизация

Обычным заблуждение является мнение, что все статически типизированные языки являются сильно типизированными, а динамически типизированные - слабо типизированными. Это неверно, и вот почему.

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

X = 1 + "2"

Мы часто ассоциируем статически типизированные языки, такие как Java и C#, с сильно типизированным (они такими и являются), поскольку тип данных задаётся явно при инициализации переменной - как в этом примере на Java:

String foo = new String("hello world");

Тем не менее, Ruby, Python и JavaScript (все они обладaют динамической типизацией) также являются сильно типизированными, хотя разработчику и не нужно указывать тип переменной при объявлении. Рассмотрим такой же пример, но написанный на Ruby:

Foo = "hello world"

Оба языка являются сильно типизированными, но используют разные методы проверки типов. Такие языки, как Ruby, Python и JavaScript не требуют явного определения типов из-за вывода типов - способности программно выводить нужный тип переменной в зависимости от её значения. Вывод типов - это отдельное свойство языка, и не относится к системам типов.

Слабо типизированный язык - это язык, в котором переменные не привязаны к конкретному типу данных; у них всё ещё есть тип, но ограничения типобезопасности гораздо слабее. Рассмотрим следующий пример кода на PHP:

$foo = "x"; $foo = $foo + 2; // not an error echo $foo; // 2

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

Миф разрушен.

Хотя статическая / динамическая и сильная / слабая системы типов и являются разными, они обе связаны с типобезопасностью. Проще всего это выразить так: первая система говорит о том, когда проверяется типобезопасность, а вторая - как.

Миф 2: статическая / динамическая типизация == компилируемые / интерпретируемые языки

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

Когда мы говорим о типизации языка, мы говорим о языке как о целом. Например, неважно, какую версию Java вы используете - она всегда будет статически типизированной. Это отличается от того случая, когда язык является компилируемым или интерпретируемым, поскольку в этом случае мы говорим о конкретной реализации языка. В теории, любой язык может быть как компилируемым, так и интерпретируемым. Самая популярная реализация языка Java использует компиляцию в байткод, который интерпретирует JVM - но есть и иные реализации этого языка, которые компилируются напрямую в машинный код или интерпретируются как есть.

Если это всё ещё непонятно, советую прочесть этого цикла.

Заключение

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

Нет однозначного ответа на вопрос “какая типизация лучше?” - у каждой есть свои преимущества и недостатки. Некоторые языки - такие как Perl и C# - даже позволяют вам самостоятельно выбирать между статической и динамической системами проверки типов. Понимание этих систем позволит вам лучше понять природу возникающих ошибок, а также упростит борьбу с ними.

Рассказать друзьям