Можно сказать, что языки первого типа пригодны, но бесполезны, вторые, возможно, полезны, но не пригодны.
На практике необходима система типов, пригодная и полезная одновременно: достаточно мощная для реализации потребностей вычислений и достаточно удобная, не заставляющая нас идти на усложнения для удовлетворения правил типизации.
Будем говорить, что язык реалистичен, если он пригоден к применению и полезен на практике. В отличие от определения статической типизации, дающего безапелляционный ответ на вопрос: "Типизирован ли язык 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. Сравнительные издержки на исправление ошибок ([Boehm 1981], публикуется с разрешения)
Читабельность или Простота понимания (readability) имеет свои преимущества. Во всех примерах этой книги появление типа у сущности дает читателю информацию о ее назначении. Читабельность крайне важна на этапе сопровождения.
Исключив читабельность из круга приоритетов, можно было бы получить другие преимущества, не вводя явных объявлений. В самом деле, возможна неявная форма типизации, когда компилятор, не требуя явного указания типа, пытается автоматически определить его из контекста применения сущности. Эта стратегия известна как выведение типов (type inference). Но в программной инженерии явные объявления типов это помощь, а не наказание, - тип должен быть ясен не только машине, но и читающему текст человеку. |
Наконец, эффективность может определять успех или отказ от объектной технологии на практике. В отсутствие статической типизации на выполнение x.f (arg) может уйти сколько угодно времени. Причина этого в том, что на этапе выполнения, не найдя f в базовом классе цели x, поиск будет продолжен у ее потомков, а это верная дорога к неэффективности. Снять остроту проблемы можно, улучшив поиск компонента по иерархии. Авторы языка Self провели большую работу, стремясь генерировать лучший код для языка с динамической типизацией. Но именно статическая типизация позволила такому ОО-продукту приблизиться или сравняться по эффективности с традиционным ПО.
Ключом к статической типизации является уже высказанная идея о том, что компилятор, генерирующий код для конструкции x.f (arg), знает тип x. Из-за полиморфизма нет возможности однозначно определить подходящую версию компонента f. Но объявление сужает множество возможных типов, позволяя компилятору построить таблицу, обеспечивающую доступ к правильному f с минимальными издержками, - с ограниченной константой сложностью доступа. Дополнительно выполняемые оптимизации статического связывания (static binding) и подстановки (inlining) - также облегчаются благодаря статической типизации, полностью устраняя издержки в тех случаях, когда они применимы.
Аргументы в пользу динамической типизации
Несмотря на все это, динамическая типизация не теряет своих приверженцев, в частности, среди Smalltalk-программистов. Их аргументы основаны прежде всего на реализме, речь о котором шла выше. Они уверены, что статическая типизация чересчур ограничивает их, не давая им свободно выражать свои творческие идеи, называя иногда ее "поясом целомудрия".
С такой аргументацией можно согласиться, но лишь для статически типизированных языков, не поддерживающих ряд возможностей. Стоит отметить, что все концепции, связанные с понятием типа и введенные в предыдущих лекциях, необходимы - отказ от любой из них чреват серьезными ограничениями, а их введение, напротив, придает нашим действиям гибкость, а нам самим дает возможность в полной мере насладиться практичностью статической типизации.