Теория языков программирования
Статическая vs динамическая типизация
2019 год. Airbnb переводит весь frontend на TypeScript. Анализ тысяч production-багов показывает: 38% из них были бы пойманы статической типизацией ещё до запуска. Каждый третий баг, который долетал до пользователей в 3 часа ночи - это TypeError или undefined-доступ, который компилятор TypeScript отловил бы за секунды. Статика vs динамика - это не вопрос вкуса. Это инженерный trade-off с измеримыми последствиями.
- TypeScript vs JavaScript - почему 78% JS-проектов переходят на TS: compile-time safety на масштабе
- Python type hints - зачем динамическому языку добавили статические типы: mypy ловит баги без смены рантайма
- Rust vs C - как строгая типизация предотвращает уязвимости безопасности (use-after-free, buffer overflow)
- Выбор языка для стартапа: скорость разработки vs надёжность в production - реальный trade-off
Как Python получил типы - история одного компромисса
2014 год. Guido van Rossum, создатель Python, лично пишет PEP 484 - Python type hints. Казалось бы странно: человек, который 25 лет строил динамический язык, добавляет в него статические аннотации. Объяснение простое: Dropbox, где работал Guido, написал mypy - статический анализатор Python-кода. В крупных кодовых базах (100K+ строк) динамика превращалась в nightmare: рефакторинг - рулетка, автодополнение в IDE - угадывание. Type hints не меняют рантайм Python - они дают инструментам (mypy, pyright, IDE) возможность статически анализировать код. Gradual typing победил pragmatically.
Предварительные знания
Статическая типизация
**Статическая типизация** - типы всех выражений известны и проверяются **до запуска** программы, на этапе компиляции. Компилятор читает код и проверяет совместимость типов. Это не строгость ради строгости - это инструмент, который работает за разработчика 24/7, даже в ветках кода, до которых тесты никогда не доберутся.
Статический type checker - как корректор, который читает рукопись до публикации. Находит целый класс ошибок ещё до того, как программа запустится. В 2019 году Airbnb перешли с JavaScript на TypeScript и обнаружили: **38% багов** из production были бы пойманы статической типизацией ещё на этапе компиляции.
**Языки со статической типизацией:** Java, C, C++, Rust, Go, Haskell, Kotlin, Swift, TypeScript. Все они проверяют типы до запуска - но с разной степенью строгости.
| Преимущество | Как это работает |
|---|---|
| Раннее обнаружение багов | Компилятор ловит ошибки типов до запуска - не нужно ждать runtime |
| IDE support | Автодополнение, рефакторинг, go-to-definition - IDE знает типы всех переменных |
| Документация в коде | Сигнатуры функций показывают, что входит и что выходит |
| Производительность | Компилятор оптимизирует код, зная типы заранее - нет runtime-проверок |
| Безопасный рефакторинг | Переименовал поле? Компилятор покажет ВСЕ места, где оно используется |
Когда статический типизатор обнаруживает ошибку типов?
Динамическая типизация
**Динамическая типизация** - типы проверяются **во время выполнения** программы. Переменная не привязана к конкретному типу: сейчас число, через строку - строка. Язык проверяет совместимость типов только когда операция реально выполняется. Это открывает возможность для быстрого прототипирования - но создаёт класс ошибок, которые прячутся до самого неудобного момента.
Гибкость динамических языков - палка о двух концах. Быстрое прототипирование: не нужно объявлять типы, код короче, итерации быстрее. Но цена - **ошибки, которые статический язык поймал бы сразу, выстреливают в самый неподходящий момент**.
«А тесты?» Тесты помогают, но покрытие **никогда не бывает 100%**. Статическая типизация проверяет ВСЕ пути выполнения, тесты - только те, которые написаны. Один пропущенный edge case - и TypeError в продакшене.
- Быстрое прототипирование — Нет аннотаций типов, код короче. Идеально для скриптов, прототипов, data science. Python-скрипт на 10 строк - без церемоний.
- Хрупкость в production — TypeError, AttributeError, undefined is not a function - классика динамических языков. Баги прячутся в редко выполняемых ветках кода.
**Языки с динамической типизацией:** Python, JavaScript, Ruby, PHP, Lua, Perl, Elixir, Clojure. Все они проверяют типы только при выполнении операций.
Какой код вызовет ошибку только в runtime, но не при компиляции?
Компромиссы и матрица типизации
Одно из самых распространённых заблуждений: **Static = Strong, Dynamic = Weak**. Это не так. «Статическая/динамическая» и «сильная/слабая» - это **две независимые оси**. Python динамический - но сильный. C статический - но слабый. JavaScript динамический и слабый. Путать их - значит неправильно выбирать инструмент для задачи.
**Сильная типизация** - язык не выполняет неявные преобразования между несовместимыми типами. **Слабая типизация** - язык тихо конвертирует типы, даже когда это приводит к абсурдным результатам.
| Сильная (Strong) | Слабая (Weak) | |
|---|---|---|
| Статическая (Static) | Java, Rust, Haskell, Kotlin | C, C++ |
| Динамическая (Dynamic) | Python, Ruby, Elixir | JavaScript, PHP, Perl |
| Критерий | Статическая | Динамическая |
|---|---|---|
| Скорость разработки | Медленнее на старте (аннотации типов) | Быстрее прототипирование |
| Надёжность | Баги ловятся при компиляции | Баги проявляются в runtime |
| Производительность | Оптимизация на этапе компиляции | Overhead на runtime-проверки типов |
| Рефакторинг | Безопасный: компилятор проверит всё | Рискованный: без тестов не понять, что сломалось |
| IDE support | Отличный: автодополнение, навигация | Ограниченный: IDE «угадывает» типы |
| Кривая обучения | Выше: нужно понимать систему типов | Ниже: просто пишешь код |
Gradual typing - компромисс эпохи
2012 год. Anders Hejlsberg (автор Delphi, C#, Turbo Pascal) выпускает TypeScript. Идея проста: начинать без типов, добавлять постепенно в критичные места. 2014 год - Python 3.5 получает type hints (PEP 484), Guido van Rossum лично пишет первый черновик. К 2024 году TypeScript используют более 78% JavaScript-проектов на GitHub. Gradual typing победил не потому что «правильный» - а потому что реальный.
Статическая типизация = сильная, динамическая = слабая
Это две независимые оси. Python - динамическая + сильная, C - статическая + слабая
«Когда проверяются типы» (static/dynamic) и «насколько строго» (strong/weak) - разные вопросы. JavaScript слабый не потому что динамический, а потому что делает безумные неявные преобразования.
Python - это какая типизация?
Утиная типизация и структурная типизация
If it walks like a duck and quacks like a duck, then it is a duck.
**Утиная типизация (duck typing)** - тип объекта не важен. Важно, **что он умеет делать**. Если объект имеет метод `quack()` - можно вызвать `quack()`, независимо от того, утка это или робот. Именно так работает Python `len()`: он не проверяет тип - он проверяет наличие `__len__`. Любой объект с `__len__` - «достаточно список».
А что, если нужен duck typing, но **со статической проверкой**? Тут появляется **структурная типизация**: компилятор проверяет не *имя* типа, а его *структуру* - какие поля и методы он имеет. TypeScript и Go используют именно этот подход - и это делает их одновременно гибкими и безопасными.
| Подход | Язык | Проверка | Когда |
|---|---|---|---|
| Duck typing | Python, Ruby | Наличие методов у объекта | В runtime |
| Structural typing | TypeScript, Go | Совпадение структуры типов | При компиляции |
| Nominal typing | Java, C#, Rust | Явное объявление implements/traits | При компиляции |
**Структурная типизация** - это «статический duck typing». Гибкость утиной типизации (не нужно явно объявлять интерфейсы) вместе с безопасностью статической проверки (ошибки ловятся до запуска).
Duck typing и динамическая типизация - одно и то же
Duck typing - конкретный подход к проверке типов по поведению. Его статический аналог - structural typing (TypeScript, Go)
Динамическая типизация - КОГДА проверяются типы (в runtime). Duck typing - КАК проверяются (по наличию методов). Go доказывает, что duck typing возможен в статическом языке через structural typing.
Какой язык использует структурную типизацию (structural typing)?
Ключевые идеи
- **Статическая типизация** проверяет типы до запуска (compile time) - ранние баги, IDE support, безопасный рефакторинг
- **Динамическая типизация** проверяет типы в runtime - быстрое прототипирование, но TypeError в продакшене
- **Strong/Weak != Static/Dynamic** - это две независимые оси. Python - динамический + сильный, C - статический + слабый
- **Duck typing** - проверка по поведению, не по имени типа. Structural typing (TypeScript, Go) - его статический аналог
Связанные темы
Статическая vs динамическая типизация - центральная ось теории языков программирования:
- Системы типов — Фундамент: что такое типы и зачем они нужны
- Вывод типов (Type Inference) — Как статические языки избавляются от явных аннотаций - лучшее из двух миров
- Gradual Typing — TypeScript, Python hints - постепенное добавление типов в динамический язык
Вопросы для размышления
- Если бы в стартапе начинали с нуля - выбрали бы статический или динамический язык? Как изменился бы ответ через 2 года, когда кодовая база вырастет в 10 раз?
- Почему Python добавил type hints, а не перешёл на статическую типизацию? Что бы потерялось?
- Можно ли сказать, что structural typing (TypeScript) - это «идеальный компромисс» между duck typing и nominal typing? Или у него есть свои минусы?
Связанные уроки
- plt-02-type-systems — Фундамент типов необходим перед сравнением статической и динамической проверки
- plt-04-type-inference — Вывод типов - способ иметь статику без explicit аннотаций
- plt-05-gradual — Gradual typing - TypeScript и Python hints как практический компромисс
- comp-18-type-checking — Type checker в компиляторе реализует именно статическую проверку
- se-01 — Выбор языка для проекта - прямое применение знаний о типизации
- plt-08-generics — Generics - продвинутая static typing feature, строящаяся на этих основах