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

  • Динами́ческая типиза́ция - приём, широко используемый в языках программирования и языках спецификации, при котором переменная связывается с типом в момент присваивания значения, а не в момент объявления переменной. Таким образом, в различных участках программы одна и та же переменная может принимать значения разных типов. Примеры языков с динамической типизацией - 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) - один из видов алгебраических типов данных, который характеризуется тем, что его конструкторы могут возвращать значения не своего типа, связанного с ним. Сконструированы под влиянием работ об индуктивных семействах в среде исследователей зависимых типов.

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

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

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

Чтобы максимально просто объяснить две абсолютно разные технологии, начнём сначала. Первое, с чем сталкивается программист при написании кода - объявление переменных. Вы можете заметить, что, например, в языке программирования 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 - уже упоминавшийся выше язык программирования, который нашел применение в веб-технологиях для создания веб-сценариев, выполняющихся на стороне клиента. Динамическая типизация используется для облегчения написания кода, ведь обычно он разбивается на небольшие блоки.

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

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

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

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

Хотя возможны и промежуточные варианты, здесь представлены два главных подхода:

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

Эти термины легко объяснимы: при динамической типизации проверка типов происходит во время работы системы (динамически), а при статической типизации проверка выполняется над текстом статически (до выполнения).

Статическая типизация предполагает автоматическую проверку, возлагаемую, как правило, на компилятор. В итоге имеем простое определение:

Определение: статически типизированный язык

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

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

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

Правила типизации

Наша ОО-нотация является статически типизированной. Ее правила типов были введены в предыдущих лекциях и сводятся к трем простым требованиям.

  • При объявлении каждой сущности или функции должен задаваться ее тип, например, acc : ACCOUNT . Каждая подпрограмма имеет 0 или более формальных аргументов, тип которых должен быть задан, например: put (x: G; i: INTEGER) .
  • В любом присваивании x:= y и при любом вызове подпрограммы, в котором y - это фактический аргумент для формального аргумента x , тип источника y должен быть совместим с типом цели x . Определение совместимости основано на наследовании: B совместим с A , если является его потомком, - дополненное правилами для родовых параметров (см. "Введение в наследование").
  • Вызов x.f (arg) требует, чтобы f был компонентом базового класса для типа цели x , и f должен быть экспортирован классу, в котором появляется вызов (см. 14.3).

Реализм

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

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

Можно сказать, что языки первого типа пригодны , но бесполезны , вторые, возможно, полезны, но не пригодны.

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

Будем говорить, что язык реалистичен , если он пригоден к применению и полезен на практике. В отличие от определения статической типизации , дающего безапелляционный ответ на вопрос: " Типизирован ли язык X статически ?", определение реализма отчасти субъективно.

В этой лекции мы убедимся, что предлагаемая нами нотация реалистична.

Пессимизм

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

Рассмотрим обычный, необъектный, Pascal-подобный язык с различными типами REAL и INTEGER . При описании n: INTEGER; r: Real оператор n:= r будет отклонен, как нарушающий правила. Так, компилятор отвергнет все нижеследующие операторы:

n:= 0.0 [A] n:= 1.0 [B] n:= -3.67 [C] n:= 3.67 - 3.67 [D]

Если мы разрешим их выполнение, то увидим, что [A] будет работать всегда, так как любая система счисления имеет точное представление вещественного числа 0,0, недвусмысленно переводимое в 0 целых. [B] почти наверняка также будет работать. Результат действия [C] не очевиден (хотим ли мы получить итог округлением или отбрасыванием дробной части?). [D] справится со своей задачей, как и оператор:

if n ^ 2 < 0 then n:= 3.67 end [E]

куда входит недостижимое присваивание (n ^ 2 - это квадрат числа n ). После замены n ^ 2 на n правильный результат даст только ряд запусков. Присваивание n большого вещественного значения, не представимого целым, приведет к отказу.

В типизированных языках все эти примеры (работающие, неработающие, иногда работающие) безжалостно трактуются как нарушения правил описания типов и отклоняются любым компилятором.

Вопрос не в том, будем ли мы пессимистами, а в том, насколько пессимистичными мы можем позволить себе быть. Вернемся к требованию реализма : если правила типов настолько пессимистичны, что препятствуют простоте записи вычислений, мы их отвергнем. Но если достижение безопасности типов достигается небольшой потерей выразительной силы, мы примем их. Например, в среде разработки, предоставляющей функции округления и выделения целой части - round и truncate , оператор n:= r считается некорректным справедливо, поскольку заставляет вас явно записать преобразование вещественного числа в целое, вместо использования двусмысленных преобразований по умолчанию.

Статическая типизация: как и почему

Хотя преимущества статической типизации очевидны, неплохо поговорить о них еще раз.

Преимущества

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

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

Раннее выявление ошибок важно еще и потому, что чем дольше мы будем откладывать их поиск, тем сильнее вырастут издержки на исправление. Это свойство, интуитивно понятное всем программистам-профессионалам, количественно подтверждают широко известные работы Бема (Boehm). Зависимость издержек на исправление от времени отыскания ошибок приведена на графике, построенном по данным ряда больших промышленных проектов и проведенных экспериментов с небольшим управляемым проектом:


Рис. 17.1.

Читабельность или Простота понимания ( readability ) имеет свои преимущества. Во всех примерах этой книги появление типа у сущности дает читателю информацию о ее назначении. Читабельность крайне важна на этапе сопровождения.

Наконец, эффективность может определять успех или отказ от объектной технологии на практике. В отсутствие статической типизации на выполнение x.f (arg) может уйти сколько угодно времени. Причина этого в том, что на этапе выполнения, не найдя f в базовом классе цели x , поиск будет продолжен у ее потомков, а это верная дорога к неэффективности. Снять остроту проблемы можно, улучшив поиск компонента по иерархии. Авторы языка Self провели большую работу, стремясь генерировать лучший код для языка с динамической типизацией. Но именно статическая типизация позволила такому ОО-продукту приблизиться или сравняться по эффективности с традиционным ПО.

Ключом к статической типизации является уже высказанная идея о том, что компилятор, генерирующий код для конструкции x.f (arg) , знает тип x . Из-за полиморфизма нет возможности однозначно определить подходящую версию компонента f . Но объявление сужает множество возможных типов, позволяя компилятору построить таблицу, обеспечивающую доступ к правильному f с минимальными издержками, - с ограниченной константой сложностью доступа. Дополнительно выполняемые оптимизации статического связывания (static binding) и подстановки (inlining) - также облегчаются благодаря статической типизации , полностью устраняя издержки в тех случаях, когда они применимы.

Аргументы в пользу динамической типизации

Несмотря на все это, динамическая типизация не теряет своих приверженцев, в частности, среди Smalltalk-программистов. Их аргументы основаны прежде всего на реализме, речь о котором шла выше. Они уверены, что статическая типизация чересчур ограничивает их, не давая им свободно выражать свои творческие идеи, называя иногда ее "поясом целомудрия".

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

Типизация: слагаемые успеха

Каковы механизмы реалистичной статической типизации ? Все они введены в предыдущих лекциях, а потому нам остается лишь кратко о них напомнить. Их совместное перечисление показывает согласованность и мощь их объединения.

Наша система типов полностью основана на понятии класса . Классами являются даже такие базовые типы, как INTEGER , а стало быть, нам не нужны особые правила описания предопределенных типов. (В этом наша нотация отличается от "гибридных" языков наподобие Object Pascal , Java и C++, где система типов старых языков сочетается с объектной технологией, основанной на классах.)

Развернутые типы дают нам больше гибкости, допуская типы, чьи значения обозначают объекты, наряду с типами, чьи значения обозначают ссылки.

Решающее слово в создании гибкой системы типов принадлежит наследованию и связанному с ним понятию совместимости . Тем самым преодолевается главное ограничение классических типизированных языков, к примеру, Pascal и Ada, в которых оператор x:= y требует, чтобы тип x и y был одинаковым. Это правило слишком строго: оно запрещает использовать сущности, которые могут обозначать объекты взаимосвязанных типов (SAVINGS_ACCOUNT и CHECKING_ACCOUNT ). При наследовании мы требуем лишь совместимости типа y с типом x , например, x имеет тип ACCOUNT , y - SAVINGS_ACCOUNT , и второй класс - наследник первого.

На практике статически типизированный язык нуждается в поддержке множественного наследования . Известны принципиальные обвинения статической типизации в том, что она не дает возможность по-разному интерпретировать объекты. Так, объект DOCUMENT (документ) может передаваться по сети, а потому нуждается в наличия компонентов, связанных с типом MESSAGE (сообщение). Но эта критика верна только для языков, ограниченных единичным наследованием .


Рис. 17.2.

Универсальность необходима, например, для описания гибких, но безопасных контейнерных структур данных (например class LIST [G] .. .). Не будь этого механизма, статическая типизация потребовала бы объявления разных классов для списков, отличающихся типом элементов.

В ряде случаев универсальность требуется ограничить , что позволяет использовать операции, применимые лишь к сущностям родового типа. Если родовой класс SORTABLE_LIST поддерживает сортировку, он требует от сущностей типа G , где G - родовой параметр, наличия операции сравнения. Это достигается связыванием с G класса, задающего родовое ограничение, - COMPARABLE :

class SORTABLE_LIST ...

Любой фактический родовой параметр SORTABLE_LIST должен быть потомком класса COMPARABLE , имеющего необходимый компонент.

Еще один обязательный механизм - попытка присваивания - организует доступ к тем объектам, типом которых ПО не управляет. Если y - это объект базы данных или объект, полученный через сеть, то оператор x ?= y присвоит x значение y , если y имеет совместимый тип, или, если это не так, даст x значение Void .

Утверждения , связанные, как часть идеи Проектирования по Контракту, с классами и их компонентами в форме предусловий, постусловий и инвариантов класса, дают возможность описывать семантические ограничения, которые не охватываются спецификацией типа . В таких языках, как Pascal и Ada, есть типы-диапазоны, способные ограничить значения сущности, к примеру, интервалом от 10 до 20, однако, применяя их, вам не удастся добиться того, чтобы значение i являлось отрицательным, всегда вдвое превышая j . На помощь приходят инварианты классов, призванные точно отражать вводимые ограничения, какими бы сложными они не были.

Закрепленные объявления нужны для того, чтобы на практике избегать лавинного дублирования кода. Объявляя y: like x , вы получаете гарантию того, что y будет меняться вслед за любыми повторными объявлениями типа x у потомка. В отсутствие этого механизма разработчики беспрестанно занимались бы повторными объявлениями, стремясь сохранить соответствие различных типов.

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

При разработке программных систем на деле необходимо еще одно свойство, присущее самой среде разработки - быстрая, возрастающая (fast incremental) перекомпиляция . Когда вы пишите или модифицируете систему, хотелось бы как можно скорее увидеть эффект изменений. При статической типизации вы должны дать компилятору время на перепроверку типов. Традиционные подпрограммы компиляции требуют повторной трансляции всей системы (и ее сборки ), и этот процесс может быть мучительно долгим, особенно с переходом к системам большого масштаба. Это явление стало аргументом в пользу интерпретирующих систем, таких как ранние среды Lisp или Smalltalk, запускавшие систему практически без обработки, не выполняя проверку типов. Сейчас этот аргумент позабыт. Хороший современный компилятор определяет, как изменился код с момента последней компиляции, и обрабатывает лишь найденные изменения.

"Типизирована ли кроха"?

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

Самой распространенной лазейкой в статически типизированных языках является наличие преобразований, меняющих тип сущности . В C и производных от него языках их называют "приведением типа" или кастингом ( cast ). Запись (OTHER_TYPE) x указывает на то, что значение x воспринимается компилятором, как имеющее тип OTHER_TYPE , при соблюдении некоторых ограничений на возможные типы.

Подобные механизмы обходят ограничения проверки типов. Приведение широко распространено при программировании на языке C, включая диалект ANSI C. Даже в языке C++ приведение типов, хотя и не столь частое, остается привычным и, возможно, необходимым делом.

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

Типизация и связывание

Хотя как читатель этой книги вы наверняка отличите статическую типизацию от статического связывания , есть люди, которым подобное не под силу. Отчасти это может быть связано с влиянием языка Smalltalk, отстаивающего динамический подход к обеим задачам и способного сформировать неверное представление, будто они имеют одинаковое решение. (Мы же в своей книге утверждаем, что для создания надежных и гибких программ желательно объединить статическую типизацию и динамическое связывание.)

Как типизация, так и связывание имеют дело с семантикой Базисной Конструкции x.f (arg) , но отвечают на два разных вопроса:

Типизация и связывание

  • Вопрос о типизации : когда мы должны точно знать, что во время выполнения появится операция, соответствующая f , применимая к объекту, присоединенному к сущности x (с параметром arg )?
  • Вопрос о связывании : когда мы должны знать, какую операцию инициирует данный вызов?

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

В рамках объектного подхода:

  • проблема, возникающая при типизации, связана с полиморфизмом : поскольку x во время выполнения может обозначать объекты нескольких различных типов, мы должны быть уверены, что операция, представляющая f , доступна в каждом из этих случаев;
  • проблема связывания вызвана повторными объявлениями : так как класс может менять наследуемые компоненты, то могут найтись две или более операции, претендующие на то, чтобы представлять f в данном вызове.

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

И динамического связывания воплощены в нотации, предложенной в этой книге.

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

Причина выбора статической типизации и динамического связывания очевидна. Первый вопрос: "Когда мы будем знать о существовании компонентов?" - предполагает статический ответ: " Чем раньше, тем лучше ", что означает: во время компиляции. Второй вопрос: "Какой из компонентов использовать?" предполагает динамический ответ: " тот, который нужен ", - соответствующий динамическому типу объекта, определяемому во время выполнения. Это единственно приемлемое решение, если статическое и динамическое связывание дает различные результаты.

При статической типизации компилятор не отклонит вызов, если можно гарантировать, что при выполнении программы к сущности my_aircraft будет присоединен объект, поставляемый с компонентом, соответствующим lower_landing_gear . Базисная техника получения гарантий проста: при обязательном объявлении my_aircraft требуется, чтобы базовый класс его типа включал такой компонент. Поэтому my_aircraft не может быть объявлен как AIRCRAFT , так как последний не имеет lower_landing_gear на этом уровне; вертолеты, по крайней мере в нашем примере, выпускать шасси не умеют. Если же мы объявим сущность как PLANE , - класс, содержащий требуемый компонент, - все будет в порядке.

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

Обязательные условия

Строгая типизация подразумевает выполнение следующих обязательных условий:

  1. Любой объект данных (переменная, константа, выражение) в языке всегда имеет строго определённый тип , который фиксируется на момент компиляции программы (статическая типизация) или определяется во время выполнения (динамическая типизация).
  2. Допускается присваивание переменной только значения, имеющего строго тот же тип данных, что и переменная, те же ограничения действуют в отношении передачи параметров и возврата результатов функций.
  3. Каждая операция требует параметров строго определённых типов.
  4. Неявное преобразование типов не допускается (то есть транслятор воспринимает любую попытку использовать значение не того типа, который был описан для переменной, параметра, функции или операции, как синтаксическую ошибку).

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

Типизация в языках программирования

Ссылки

См. также


Wikimedia Foundation . 2010 .

Смотреть что такое "Строгая типизация" в других словарях:

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

    Типизация данных Типобезопасность Вывод типов Динамическая типизация Статическая типизация Строгая типизация Мягкая типизация Зависимые типы Утиная типизация Основная статья: Строгая типизация Динамическая типизация приём, широко… … Википедия

    Типизация данных Типобезопасность Вывод типов Динамическая типизация Статическая типизация Строгая типизация Мягкая типизация Зависимые типы Утиная типизация Основная статья: Строгая типизация Статическая типизация приём, широко… … Википедия

    Динамическая типизация приём, широко используемый в языках программирования и языках спецификации, при котором переменная связывается с типом в момент присваивания значения, а не в момент объявления переменной. Таким образом, в различных участках … Википедия

    Типизация данных Типобезопасность Вывод типов Динамическая типизация Статическая типизация Строгая типизация Мягкая типизация Зависимые типы Утиная типизация Вывод типа (англ. Type inference) в программировании возможность компилятора… … Википедия

    Типизация данных Типобезопасность Вывод типов Динамическая типизация Статическая типизация Строгая типизация Мягкая типизация Зависимые типы Утиная типизация Зависимый тип, в информатике и логике тип, который зависит от значения. Зависимые… … Википедия

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

    Тип данных Содержание 1 История 2 Определение 3 Необходимость использования типов данных … Википедия

    У этого термина существуют и другие значения, см. ML (значения). ML Семантика: мультипарадигменный: функциональный, императивный, модульный Появился в: 1973 Автор(ы): Робин Милнер и др. Эдинбургский университет … Википедия

Типизация - назначения типа информационным сущностям.

Наиболее распространённые примитивные типы данных:

  • Числовой
  • Символьный
  • Логический

Основные функции системы типов данных:

  • Обеспечение безопасности
    Проверяется каждая операция на получение аргументов именно тех типов, для которых она имеет предназначена;
  • Оптимизация
    На основе типа выбирается способ эффективного хранения и алгоритмов его обработки;
  • Документация
    Подчеркивается намерения программиста;
  • Абстракция
    Использование типов данных высокого уровня позволяет программисту думать о значениях как о высокоуровневых сущностях, а не как о наборе битов.

Классификация

Есть множество классификаций типизаций языков программирования, но основные только 3:

Статическая / динамическая типизация

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

Динамическая типизация является противоположностью статической типизации. В динамической типизации все типы выясняются во время выполнения программы.

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

Динамическая типизация

Var luckyNumber = 777; var siteName = "Tyapk"; // подразумеваем число, записываем строку var wrongNumber = "999";

Статическая типизация

Let luckyNumber: number = 777; let siteName: string = "Tyapk"; // вызовет ошибку let wrongNumber: number = "999";

  • Статическая: Java, C#, TypeScript.
  • Динамическая: Python, Ruby, JavaScript.

Явная / неявная типизация.

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

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

Неявная типизация

Let stringVar = "777" + 99; // получим "77799"

Явная типизация (вымышленный язык похожий на JS)

Let wrongStringVar = "777" + 99; // вызовет ошибку let stringVar = "777" + String(99); // получим "77799"

Строгая / нестрогая типизация

Также называется сильная / слабая типизация. При строгой типизации типы назначаются «раз и навсегда», при нестрогой могут изменяться в процессе выполнения программы.

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

Строгая типизация (вымышленный язык похожий на JS)

Let wrongNumber = 777; wrongNumber = wrongNumber + "99"; // получим ошибку, что к числовой переменной wrongNumber прибавляется строка let trueNumber = 777 + Number("99"); // получим 876

Нестрогая типизация (как есть в js)

Let wrongNumber = 777; wrongNumber = wrongNumber + "99"; // получили строку "77799"

  • Строгая: Java, Python, Haskell, Lisp.
  • Нестрогая: C, JavaScript, Visual Basic, PHP.