Ликбез по типизации в языках программирования

Эта статья содержит необходимый минимум тех вещей, которые просто необходимо знать о типизации, чтобы не называть динамическую типизацию злом, Lisp — бестиповым языком, а C — языком со строгой типизацией.
В полной версии находится подробное описание всех видов типизации, приправленное примерами кода, ссылками на популярные языки программирования и показательными картинками.
Рекомендую прочитать сначала краткую версию статьи, а затем при наличии желания и полную.
Краткая версия
Языки программирования по типизации принято делить на два больших лагеря — типизированные и нетипизированные (бестиповые). К первому например относятся C, Python, Scala, PHP и Lua, а ко второму — язык ассемблера, Forth и Brainfuck.
Так как «бестиповая типизация» по своей сути — проста как пробка, дальше она ни на какие другие виды не делится. А вот типизированные языки разделяются еще на несколько пересекающихся категорий:
-
Статическая / динамическая типизация. Статическая определяется тем, что конечные типы переменных и функций устанавливаются на этапе компиляции. Т.е. уже компилятор на 100% уверен, какой тип где находится. В динамической типизации все типы выясняются уже во время выполнения программы.
Примеры:
Статическая: C, Java, C#;
Динамическая: Python, JavaScript, Ruby.
Примеры:
Сильная: Java, Python, Haskell, Lisp;
Слабая: C, JavaScript, Visual Basic, PHP.
Тем-не менее не бывает языков со статической и динамической типизаций одновременно. Хотя забегая вперед скажу, что тут я вру — они действительно существуют, но об этом позже.
Подробная версия
Если краткой версии Вам показалось недостаточно, хорошо. Не зря же я писал подробную? Главное, что в краткой версии просто невозможно было уместить всю полезную и интересную информацию, а подробная будет возможно слишком длинной, чтобы каждый смог ее прочесть, не напрягаясь.
Бестиповая типизация
В бестиповых языках программирования — все сущности считаются просто последовательностями бит, различной длины.
Бестиповая типизация обычно присуща низкоуровневым (язык ассемблера, Forth) и эзотерическим (Brainfuck, HQ9, Piet) языкам. Однако и у нее, наряду с недостатками, есть некоторые преимущества.
Преимущества
- Позволяет писать на предельно низком уровне, причем компилятор / интерпретатор не будет мешать какими-либо проверками типов. Вы вольны производить любые операции над любыми видами данных.
- Получаемый код обычно более эффективен.
- Прозрачность инструкций. При знании языка обычно нет сомнений, что из себя представляет тот или иной код.
Недостатки
- Сложность. Часто возникает необходимость в представлении комплексных значений, таких как списки, строки или структуры. С этим могут возникнуть неудобства.
- Отсутствие проверок. Любые бессмысленные действия, например вычитание указателя на массив из символа будут считаться совершенно нормальными, что чревато трудноуловимыми ошибками.
- Низкий уровень абстракции. Работа с любым сложным типом данных ничем не отличается от работы с числами, что конечно будет создавать много трудностей.
Сильная безтиповая типизация?
Да, такое существует. Например в языке ассемблера (для архитектуры х86/х86-64, других не знаю) нельзя ассемблировать программу, если вы попытаетесь загрузить в регистр cx (16 бит) данные из регистра rax (64 бита).
mov cx, eax ; ошибка времени ассемблирования
Так получается, что в ассемлере все-таки есть типизация? Я считаю, что этих проверок недостаточно. А Ваше мнение, конечно, зависит только от Вас.
Статическая и динамическая типизации

Главное, что отличает статическую (static) типизацию от динамической (dynamic) то, что все проверки типов выполняются на этапе компиляции, а не этапе выполнения.
Некоторым людям может показаться, что статическая типизация слишком ограничена (на самом деле так и есть, но от этого давно избавились с помощью некоторых методик). Некоторым же, что динамически типизированные языки — это игра с огнем, но какие же черты их выделяют? Неужели оба вида имеют шансы на существование? Если нет, то почему много как статически, так и динамически типизированных языков?
Преимущества статической типизации
- Проверки типов происходят только один раз — на этапе компиляции. А это значит, что нам не нужно будет постоянно выяснять, не пытаемся ли мы поделить число на строку (и либо выдать ошибку, либо осуществить преобразование).
- Скорость выполнения. Из предыдущего пункта ясно, что статически типизированные языки практически всегда быстрее динамически типизированных.
- При некоторых дополнительных условиях, позволяет обнаруживать потенциальные ошибки уже на этапе компиляции.
- Ускорение разработки при поддержке IDE (отсеивание вариантов, заведомо не подходящих по типу).
Преимущества динамической типизации

- Простота создания универсальных коллекций — куч всего и вся (редко возникает такая необходимость, но когда возникает динамическая типизация выручит).
- Удобство описания обобщенных алгоритмов (например сортировка массива, которая будет работать не только на списке целых чисел, но и на списке вещественных и даже на списке строк).
- Легкость в освоении — языки с динамической типизацией обычно очень хороши для того, чтобы начать программировать.
Обобщенное программирование
Хорошо, самый важный аргумент за динамическую типизацию — удобство описания обобщенных алгоритмов. Давайте представим себе проблему — нам нужна функция поиска по нескольким массивам (или спискам) — по массиву целых чисел, по массиву вещественных и массиву символов.
Как же мы будем ее решать? Решим ее на 3-ех разных языках: одном с динамической типизацией и двух со статической.
Алгоритм поиска я возьму один из простейших — перебор. Функция будет получать искомый элемент, сам массив (или список) и возвращать индекс элемента, или, если элемент не найден — (-1).
Динамическое решение (Python):
def find( required_element, list ): for (index, element) in enumerate(list): if element == required_element: return index return (-1)
Как видите, все просто и никаких проблем с тем, что список может содержать хоть числа, хоть списки, хоть другие массивы нет. Очень хорошо. Давайте пойдем дальше — решим эту-же задачу на Си!
Статическое решение (Си):
unsigned int find_int( int required_element, int array[], unsigned int size ) < for (unsigned int i = 0; i < size; ++i ) if (required_element == array[i]) return i; return (-1); >unsigned int find_float( float required_element, float array[], unsigned int size ) < for (unsigned int i = 0; i < size; ++i ) if (required_element == array[i]) return i; return (-1); >unsigned int find_char( char required_element, char array[], unsigned int size )
Ну, каждая функция в отдельности похожа на версию из Python, но почему их три? Неужели статическое программирование проиграло?
И да, и нет. Есть несколько методик программирования, одну из которых мы сейчас рассмотрим. Она называется обобщенное программирование и язык C++ ее неплохо поддерживает. Давайте посмотрим на новую версию:
Статическое решение (обобщенное программирование, C++):
template unsigned int find( T required_element, std::vector array )
Хорошо! Это выглядит не сильно сложнее чем версия на Python и при этом не пришлось много писать. Вдобавок мы получили реализацию для всех массивов, а не только для 3-ех, необходимых для решения задачи!
Эта версия похоже именно то, что нужно — мы получаем одновременно плюсы статической типизации и некоторые плюсы динамической.
Здорово, что это вообще возможно, но может быть еще лучше. Во-первых обобщенное программирование может быть удобнее и красивее (например в языке Haskell). Во-вторых помимо обобщенного программирования также можно применить полиморфизм (результат будет хуже), перегрузку функций (аналогично) или макросы.
Статика в динамике
Также нужно упомянуть, что многие статические языки позволяют использовать динамическую типизацию, например:
- C# поддерживает псевдо-тип dynamic.
- F# поддерживает синтаксический сахар в виде оператора ?, на базе чего может быть реализована имитация динамической типизации.
- Haskell — динамическая типизация обеспечивается модулем Data.Dynamic.
- Delphi — посредством специального типа Variant.
- Common Lisp — декларации типов.
- Perl — с версии 5.6, довольно ограниченно.
Сильная и слабая типизации

Языки с сильной типизацией не позволяют смешивать сущности разных типов в выражениях и не выполняют никаких автоматических преобразований. Также их называют «языки с строгой типизацией». Английский термин для этого — strong typing.
Слабо типизированные языки, наоборот всячески способствуют, чтобы программист смешивал разные типы в одном выражении, причем компилятор сам приведет все к единому типу. Также их называют «языки с нестрогой типизацией». Английский термин для этого — weak typing.
Слабую типизацию часто путают с динамической, что совершенно неверно. Динамически типизированный язык может быть и слабо и сильно типизирован.
Однако мало, кто придает значение строгости типизации. Часто заявляют, что если язык статически типизирован, то Вы сможете отловить множество потенциальных ошибок при компиляции. Они Вам врут!
Язык при этом должен иметь еще и сильную типизацию. И правда, если компилятор вместо сообщения об ошибке будет просто прибавлять строку к числу, или что еще хуже, вычтет из одного массива другой, какой нам толк, что все «проверки» типов будут на этапе компиляции? Правильно — слабая статическая типизация еще хуже, чем сильная динамическая! (Ну, это мое мнение)
Так что-же у слабой типизации вообще нет плюсов? Возможно так выглядит, однако несмотря на то, что я ярый сторонник сильной типизации, должен согласиться, что у слабой тоже есть преимущества.
Хотите узнать какие?
Преимущества сильной типизации
- Надежность — Вы получите исключение или ошибку компиляции, взамен неправильного поведения.
- Скорость — вместо скрытых преобразований, которые могут быть довольно затратными, с сильной типизацией необходимо писать их явно, что заставляет программиста как минимум знать, что этот участок кода может быть медленным.
- Понимание работы программы — опять-же, вместо неявного приведения типов, программист пишет все сам, а значит примерно понимает, что сравнение строки и числа происходит не само-собой и не по-волшебству.
- Определенность — когда вы пишете преобразования вручную вы точно знаете, что вы преобразуете и во что. Также вы всегда будете понимать, что такие преобразования могут привести к потере точности и к неверным результатам.
Преимущества слабой типизации
- Удобство использования смешанных выражений (например из целых и вещественных чисел).
- Абстрагирование от типизации и сосредоточение на задаче.
- Краткость записи.
Оказывается есть и даже два.
Неявное приведение типов, в однозначных ситуациях и без потерь данных
Ух… Довольно длинный пункт. Давайте я буду дальше сокращать его до «ограниченное неявное преобразование» Так что же значит однозначная ситуация и потери данных?
Однозначная ситуация, это преобразование или операция в которой сущность сразу понятна. Вот например сложение двух чисел — однозначная ситуация. А преобразование числа в массив — нет (возможно создастся массив из одного элемента, возможно массив, с такой длинной, заполненный элементами по-умолчанию, а возможно число преобразуется в строку, а затем в массив символов).
Потеря данных это еще проще. Если мы преобразуем вещественное число 3.5 в целое — мы потеряем часть данных (на самом деле эта операция еще и неоднозначная — как будет производиться округление? В большую сторону? В меньшую? Отбрасывание дробной части?).
Преобразования в неоднозначных ситуациях и преобразования с потерей данных — это очень, очень плохо. Ничего хуже этого в программировании нет.
Если вы мне не верите, изучите язык PL/I или даже просто поищите его спецификацию. В нем есть правила преобразования между ВСЕМИ типами данных! Это просто ад!
Ладно, давайте вспомним про ограниченное неявное преобразование. Есть ли такие языки? Да, например в Pascal Вы можете преобразовать целое число в вещественное, но не наоборот. Также похожие механизмы есть в C#, Groovy и Common Lisp.
Ладно, я говорил, что есть еще способ получить пару плюсов слабой типизации в сильном языке. И да, он есть и называется полиморфизм конструкторов.
Я поясню его на примере замечательного языка Haskell.
Полиморфные конструкторы появились в результате наблюдения, что чаще всего безопасные неявные преобразования нужны при использовании числовых литералов.
Например в выражении pi + 1 , не хочется писать pi + 1.0 или pi + float(1) . Хочется написать просто pi + 1 !
И это сделано в Haskell, благодаря тому, что у литерала 1 нет конкретного типа. Это ни целое, ни вещественное, ни комплексное. Это же просто число!
В итоге при написании простой функции sum x y , перемножающей все числа от x до y (с инкрементом в 1), мы получаем сразу несколько версий — sum для целых, sum для вещественных, sum для рациональных, sum для комплексных чисел и даже sum для всех тех числовых типов что Вы сами определили.
Конечно спасает этот прием только при использовании смешанных выражений с числовыми литералами, а это лишь верхушка айсберга.
Таким образом можно сказать, что лучшим выходом будет балансирование на грани, между сильной и слабой типизацией. Но пока идеальный баланс не держит ни один язык, поэтому я больше склоняюсь к сильно типизированным языкам (таким как Haskell, Java, C#, Python), а не к слабо типизированным (таким как C, JavaScript, Lua, PHP).
Ладно, пойдем дальше?
Явная и неявная типизации

Язык с явной типизацией предполагает, что программист должен указывать типы всех переменных и функций, которые объявляет. Английский термин для этого — explicit typing.
Язык с неявной типизацией, напротив, предлагает Вам забыть о типах и переложить задачу вывода типов на компилятор или интерпретатор. Английски термин для этого — implicit typing.
По-началу можно решить, что неявная типизация равносильна динамической, а явная — статической, но дальше мы увидим, что это не так.
Есть ли плюсы у каждого вида, и опять же, есть ли их комбинации и есть ли языки с поддержкой обоих методов?
Преимущества явной типизации
- Наличие у каждой функции сигнатуры (например int add(int, int) ) позволяет без проблем определить, что функция делает.
- Программист сразу записывает, какого типа значения могут храниться в конкретной переменной, что снимает необходимость запоминать это.
Преимущества неявной типизации
- Сокращение записи — def add(x, y) явно короче, чем int add( int x, int y) .
- Устойчивость к изменениям. Например если в функции временная переменная была того-же типа, что и входной аргумент, то в явно типизированном языке при изменении типа входного аргумента нужно будет изменить еще и тип временной переменной.
Явная типизация по-выбору
Есть языки, с неявной типизацией по-умолчанию и возможностью указать тип значений при необходимости. Настоящий тип выражения транслятор выведет автоматически. Один из таких языков — Haskell, давайте я приведу простой пример, для наглядности:
-- Без явного указания типа add (x, y) = x + y -- Явное указание типа add :: (Integer, Integer) -> Integer add (x, y) = x + y
Примечание: я намерено использовал некаррированную функцию, а также намерено записал частную сигнатуру вместо более общей add :: (Num a) => a -> a -> a *, т.к. хотел показать идею, без объяснения синтаксиса Haskell’а.
* Спасибо int_index за нахождение ошибки.
Хм. Как мы видим, это очень красиво и коротко. Запись функции занимает всего 18 символов на одной строчке, включая пробелы!
Однако автоматический вывод типов довольно сложная вещь, и даже в таком крутом языке как Haskell, он иногда не справляется. (как пример можно привести ограничение мономорфизма)
Есть ли языки с явной типизацией по-умолчанию и неявной по-необходимости? Кон
ечно.
Неявная типизация по-выбору
В новом стандарте языка C++, названном C++11 (ранее назывался C++0x), было введено ключевое слово auto, благодаря которому можно заставить компилятор вывести тип, исходя из контекста:
Давайте сравним: // Ручное указание типа unsigned int a = 5; unsigned int b = a + 3; // Автоматический вывод типа unsigned int a = 5; auto b = a + 3;
Неплохо. Но запись сократилась не сильно. Давайте посмотрим пример с итераторами (если не понимаете, не бойтесь, главное заметьте, что запись благодаря автоматическому выводу очень сильно сокращается):
// Ручное указание типа std::vector vec = randomVector( 30 ); for ( std::vector::const_iterator it = vec.cbegin(); . ) < . >// Автоматический вывод типа auto vec = randomVector( 30 ); for ( auto it = vec.cbegin(); . )
Ух ты! Вот это сокращение. Ладно, но можно ли сделать что-нибудь в духе Haskell, где тип возвращаемого значения будет зависеть от типов аргументов?
И опять ответ да, благодаря ключевому слову decltype в комбинации с auto:
// Ручное указание типа int divide( int x, int y ) < . >// Автоматический вывод типа auto divide( int x, int y ) -> decltype(x / y)
Может показаться, что эта форма записи не сильно хороша, но в комбинации с обобщенным программированием (templates / generics) неявная типизация или автоматический вывод типов творят чудеса.
Некоторые языки программирования по данной классификации
Я приведу небольшой список из популярных языков и напишу как они подразделяются по каждой категории “типизаций”.
JavaScript - Динамическая | Слабая | Неявная Ruby - Динамическая | Сильная | Неявная Python - Динамическая | Сильная | Неявная Java - Статическая | Сильная | Явная PHP - Динамическая | Слабая | Неявная C - Статическая | Слабая | Явная C++ - Статическая | Слабая | Явная Perl - Динамическая | Слабая | Неявная Objective-C - Статическая | Слабая | Явная C# - Статическая | Сильная | Явная Haskell - Статическая | Сильная | Неявная Common Lisp - Динамическая | Сильная | Неявная D - Статическая | Сильная | Явная Delphi - Статическая | Сильная | Явная
- C# — поддерживает динамическую типизацию, посредством специального псевдо-типа dynamic с версии 4.0. Поддерживает неявную типизацию с помощью dynamic и var.
- С++ — после стандарта C++11 получил поддержку неявной типизации с помощью ключевых слов auto и decltype. Поддерживает динамическую типизацию, при использовании библиотеки Boost (boost::any, boost::variant). Имеет черты как сильной так и слабой типизации.
- Common Lisp — стандарт предусматривает декларации типов, которые некоторые реализации могут использовать также для статической проверки типов.
- D — также поддерживает неявную типизацию.
- Delphi — поддерживает динамическую типизацию посредством специального типа Variant.
Возможно я где-то ошибся, особенно с CL, PHP и Obj-C, если по какому-то языку у Вас другое мнение — напишите в комментариях.
Заключение
Окей. Уже скоро будет светло и я чувствую, что про типизацию больше нечего сказать. Ой как? Тема бездонная? Очень много осталось недосказано? Прошу в комментарии, поделитесь полезной информацией.
Типизация — Введение в программирование
В одном из прошлых уроков мы говорили об ошибках и как с ними справляться. Есть несколько видов ошибок, и я хочу напомнить об одном конкретном виде. Вот небольшой фрагмент того урока:
Взгляните на этот код:
const length = 12; const num = length(54);
Сначала мы создали константу. Помните, что это как давать чему-то название: в нашем случае — числу 12 даётся название length . В следующей строке мы вызываем функцию length и передаём ей аргумент — число 54. Но подождите! length — это не функция! Это всего лишь число. Числа — это не функции, не ящики, которые производят какие-то действия. И JavaScript пожалуется именно на это:
= length(-54); ^ TypeError: length is not a function at Object. (/Users/rakhim/test.js:2:13) at Module._compile (module.js:571:32) at Object.Module._extensions..js (module.js:580:10) at Module.load (module.js:488:32) at tryModuleLoad (module.js:447:12) at Function.Module._load (module.js:439:3) at Module.runMain (module.js:605:10) at run (bootstrap_node.js:420:7) at startup (bootstrap_node.js:139:9) at bootstrap_node.js:535:3
Это Ошибка типизации: тип объекта, который вы использовали, неверный. Интерпретатор JavaScript не скажет чем что-то является, но точно скажет чем оно не является. length — это не функция.
Ошибка типизации — это как просить кошку постирать бельё. Возможно, вы хотели попросить об этом вашего друга.
В программировании «типизация» — это классификация информации. Это общий термин и разные языки программирования справляются с типизацией по-разному. Как вы уже знаете, JavaScript умеет отличать типы. Функция — это один тип, Число — другой, и вы не можете просто использовать число как функцию.
typeof — это специальный оператор, который возвращает строку, в которой написан тип.
typeof 42; // 'number' typeof 3.14; // 'number' typeof NaN; // 'number' typeof 'Berry'; // 'string' typeof true; // 'boolean' typeof false; // 'boolean'
42 и 3.14, очевидно, числа, несколько комбинаций букв в кавычках — строка, а true и false — булево значение. Всё это — типы в JavaScript — число, строка и булево значение.
NaN означает — «не число», но тип NaN — это «число». Да, я знаю. Еще одна странность JavaScript. Такие правила в этом языке.
Типизация полезна. Когда мы попытаемся запустить число, как будто это функция, JavaScript начнёт жаловаться и мы увидим ошибку и починим её. Если бы никакого обозначения типов в JavaScript не было, мы бы сталкивались либо с каким-нибудь аномальным поведением, либо с мистической ошибкой. Вместо чёткого «length — это не функция», мы бы видели что-то вроде «I’m sorry Dave, I’m afraid I can’t do that».
А что, если создать переменную, но не задать ей никакого значения? Какой в этом случае будет тип? Это ни число, ни строка, ничто. Потому что нет значения, правильно?
JavaScript в этом случае кое-что делает в тайне от вас. Переменная без значения на самом деле имеет специальное значение — «undefined». И тип такой переменной называется «undefined».
let a; console.log(a); // undefined typeof a; // 'undefined'
Например, тип number имеет множество потенциальных значений: 1, 2, -10, 69000 и другие числа. А тип undefined только одно — undefined .
Когда дело касается типизации в программировании, важно различать две концепции: динамическая против статической и слабая против сильной.
Чтобы понимать разницу между динамической и статической типизацией, нам сначала нужно посмотреть как написанные программы становятся запущенными программами.
Код, который вы пишете, обычно конвертируется в понятную для запуска компьютером форму. Этот процесс называется компиляцией, а промежуток времени, за который это происходит — «стадией компиляции» или compile time.
После того, как компиляция закончена и программа запущена, начинается отсчёт времени, который называется «стадией исполнения» или run time.
Некоторые языки проверяют типы и ищут ошибки типизации на стадии компиляции. У них статическая типизация.
Другие языки проверяют типы и ищут ошибки типизации на стадии исполнения. Такая типизация — динамическая.
Иными словами: статическая типизация означает проверку типов перед запуском программы, динамическая — проверку типов, когда программа запущена.
C#, C++, Java, Go — статически типизированные языки. Если в одном из этих языков вы создадите число и попытаетесь проводить с ним операции, как с функцией, вы получите ошибку во время компиляции, а программа не станет запускаться — она даже не дойдёт до этой стадии, потому что ошибка типизации будет обнаружена перед исполнением, в период компиляции.
JavaScript, Ruby, PHP — динамически типизированные языки. Как вы видели раньше, если использовать неверную типизацию, ваша программа запустится, а ошибка обнаружится только когда будет исполняться конкретная строчка кода. Здесь типы проверяются в период исполнения.
Вообще-то, в JavaScript обычно нет никакой компиляции, но это тема другого урока.
Динамическая типизация не хуже и не лучше статической. Оба способа имеют свои преимущества и недостатки. Динамически типизированные языки обычно проще изучать и писать на них программы, но, как вы можете представить, это потенциально увеличивает ошибки.
Теперь давайте поговорим о слабой и сильной типизации. Посмотрите на этот JavaScript код:
4 + '7'; // '47' 4 * '7'; // 28 2 + true; // 3 false - 3; // -3
М-да… Это… Ок, что тут происходит? Сложение числа 4 со строкой «7» даёт нам строку «47». JavaScript конвертирует число 4 в строку «4» и конкатенирует две строки — склеивает их друг с другом. JavaScript просто берёт на себя ответственность предположить, что это то, что мы хотели. Глупо обвинять его — чего мы действительно хотели? Складывать число со строкой не имеет никакого смысла. Какой-нибудь другой язык, вроде Ruby или Python просто бы пожаловался и ничего не сделал.
Произведение числа 4 со строкой «7», это, как видите, 28, по мнению JavaScript. В этом случае он сконвертировал строку «7» в число 7 и произвёл обычное умножение.
JavaScript постоянно так делает. Он знает о типах разных значений, но когда типы не соответствуют, он пытается предположить и сконвертировать один тип в другой, не предупреждая вас. Иногда это полезно, иногда мозгодробяще. Такое происходит потому что JavaScript — язык со слабой типизацией. У него есть представление о типах, но он типа «это всего лишь игра, чего ты злишься?»
У этой концепции нет ничего общего с динамической и статической типизацией, смысл которых — КОГДА проверять типы. Сильная против слабой — это НАСКОЛЬКО СЕРЬЁЗНО проверять типы.
Вы можете считать, что слабая — это нестрогая типизация, а сильная — это требовательная.
В отличие от динамичности-статичности, сила типизации это спектр. У PHP типизация немного сильнее. У Python ещё сильнее. И все они динамически типизированные языки.
JavaScript делает множество неявных конвертаций, но он так же даёт нам инструменты, чтобы мы могли делать явные конвертации сами. Мы можем конвертировать строки в числа, числа в строки, булевы значения в строки и так далее:
// Конвертация числа в строку String(44843); // '44843' // Конвертация строки в число Number('590'); // 590 Number('aaa!!'); // NaN // Конвертация числа в булево значение Boolean(1); // true Boolean(0); // false // Конвертация булева значения в строку String(true); // 'true' String(false); // 'false'
Можно предположить, что неявная конверсия из типа в тип — не самая лучшая идея. Неявный, значит скрытый, а скрытый — значит трудно понимаемый и предрасположенный к ошибкам. Поведение программы становится менее очевидным. Вы пишете меньше кода, да, но код более хрупкий и менее понятный.
Дополнение к уроку
null
В JavaScript кроме undefined существует null . Оно означает, что «значение отсутствует». Например, если создать переменную, но не задавать ей значения, то у нее будет значение undefined:
let a; console.log(a); // undefined
Тут значения не оказалось ненамеренно. Видимо, просто еще не пришло время дать этой переменной значение.
null нужен для явного, намеренного указания, что значения нет. Можно сказать let a = null; . Например, вы попросили пользователя ввести информацию, но он ничего не ввел. В таком случае уместно записать в результат null .
null , в отличие от undefined , можно задавать вручную, передавать как аргумент в функцию и в целом использовать как любое другое явное значение.
( undefined тоже можно задавать вручную, но никогда не нужно этого делать: это значение семантически создано только для того, чтобы его генерировал компьютер, а не программист).
При сравнении null и undefined нужно быть осторожным:
typeof null; // "object" (не "null" по историческим причинам) typeof undefined; // "undefined" null === undefined; // false null == undefined; // true null === null; // true null == null; // true !null; // true isNaN(1 + null); //false isNaN(1 + undefined); //true
Сравнение
В этом курсе мы сравниваем данные, используя три знака равенства:
a === b; 12 === 12;
Это сравнение прямое: являются ли эти данные абсолютно идентичными?
В JavaScript есть расслабленное сравнение, с двумя знаками равенства. Оно показывает, что происходит внутри JavaScript, при сравнении значений разных типов:
1 === '1'; // false 1 == '1'; // true true === 1; // false true == 1; // true
Выводы
Типизация в JavaScript
JavaScript имеет представление о типах: числах, строках, функциях, логических значениях и так далее. typeof возвращает строку, в которой записан тип:
typeof 42; // 'number' typeof 3.14; // 'number' typeof NaN; // 'number' typeof 'Berry'; // 'string' typeof true; // 'boolean' typeof false; // 'boolean'
NaN означает «не число», но тип этого значения — number .
Переменная без значения имеет специальное значение undefined . Тип такой переменной — undefined :
let a; console.log(a); // undefined typeof a; // 'undefined'
Динамическая и статическая типизация
Код конвертируется в другую форму, которую компьютер может запустить. Этот процесс называется компиляцией, а период времени, за который этот процесс происходит — стадией компиляции (compile time).
После того, как компиляция закончена, запускается программа и период, пока она запущена, называется стадией исполнения (run time).
Статически типизированные языки проверяют типы и ищут ошибки типизации на стадии компиляции.
Динамически типизированные языки проверяют типы и ищут ошибки типизации на стадии исполнения.
Иными словами: статическое типизирование означает проверку типов перед запуском программы; динамическое — проверку типов пока программа запущена.
Слабая и сильная типизация
JavaScript часто конвертирует типы автоматически:
4 + '7'; // '47' 4 * '7'; // 28 2 + true; // 3 false - 3; // -3
JavaScript — это язык со слабой типизацией. У него есть представление о типах, но он расслаблено к ним относится и может оперировать значениями, можно сказать, произвольно. Чем сильнее система типизации, тем строже правила.
Явные конверсии в JavaScript
Number('590'); // 590 Number('aaa!!'); // NaN Boolean(1); // true Boolean(0); // false String(true); // 'true' String(false); // 'false' String(44843); // '44843'
Дополнительные материалы
- Wat by Gary Bernhardt (video)
- typeof
- Data type / Wikipedia
- Dynamic typing (WikiWikiWeb)
- Weak And Strong Typing (WikiWikiWeb)
![]()
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Об обучении на Хекслете
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Урок «Как эффективно учиться на Хекслете»
- Вебинар « Как самостоятельно учиться »
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Какой вид типизации используется в JavaScript? Динамическая слабая? Или же Статическая? Или Динамическая сильная?
JavaScript является слабо типизированным или динамическим языком. Это значит, что вам не нужно определять тип переменной заранее. Тип определится автоматически во время выполнения программы. Также это значит, что вы можете использовать одну переменную для хранения данных различных типов:
https://developer.mozilla.org/ru/docs/Web/JavaScript/Data_structures
Остальные ответы
В JS типизация слабая. Это значит, что в том случае, когда это кажется разумным, операнды автоматически преобразуются в данные с необходимым типом для выполнения
операции.
Похожие вопросы
Лекция 6б. Типы данных и типизация
Тип данных — множество значений, множество операций над ними и способ хранения в памяти компьютера (машинное представление).
Абстрактный тип данных — множество значений и множество операций над ними, т.е. способ хранения не задан.
Первая классификация типов данных:
- Простые — неделимые порции данных: число, символ, литера.
- Составные — содержащие значения других типов: cons-ячейка, список, вектор, строка.
Вторая классификация типов данных:
- Встроенные типы данных — уже заранее есть в языке.
- Пользовательские — их определяет пользователь.
В ряде языков программирования (например, в Си) есть встроенные в язык средства для определения пользовательских типов данных. Например, в Си встроены различные числовые типы. Пользователь на их основе может создавать массивы, массивы массивов, структуры, объединения и т.д.
В языке Scheme нет языковых средств для определения новых типов данных. Вместо этого пользователь придумывает способ представления некоторого значения при помощи встроенных типов данных и описывает операции над ним в виде набора процедур (иногда, макросов).
Т.е. проектируем представление типа данных и набор операций. При этом не рекомендуется работать с типом данных в обход предоставленных операций.
Если мы задокументируем только набор операций, но не опишем представление, то мы создали абстрактный тип данных.
Для типов данных языка Scheme обычно определены четыре вида операций:
- конструктор — процедура, имя которой имеет вид make-‹имя-типа› , например, make-vector , make-set (см. дз), конструктор предназначен для создания новых значений данного типа,
- предикат типа — процедура, возвращающая #t , если её аргумент является значением данного типа, имеет имя ‹имя-типа›? : vector? , set? (см. дз), multi-vector? (см. дз),
- модификаторы — операции, меняющие на месте содержимое объекта, их имя имеет вид ‹тип›-‹операция›! , например, vector-set! , multivector-set! (см. дз),
- прочие операции имеют имя вида ‹тип›-‹операция› , vector-ref , set-union , string-append и т.д.
Пользовательские типы данных часто представляют как списки, первым элементом которых является символ с именем типа, а остальные — хранимые значения.
Пример. Тип данных — круг.
(define (make-circle x y r) (list 'cicrle x y r)) (define (circle? c) (and (list? c) (equal? (car c) 'circle))) (define (circle-center c) (list (cadr c) (caddr c))) ; (cadr xs) = (car (cdr xs)) (define (circle-radius c) (cadddr c)) (define (circle-set-center! c p) (let ((x (car p)) (y (cadr p))) (set-car! (cdr c) x) (set-car! (cddr c) y) c)) (define (circle-set-radius! c r) (set-car! (cdddr c) r) c)
Типизация и системы типов
Система типов — совокупность правил в языках программирования, назначающих свойства, именуемые типами, различным конструкциям, составляющим программу — переменные, выражения, функции и модули.
Определение по Пирсу, система типов — разрешимый синтаксический метод доказательства отсутствия определённых поведений программы путём классификации конструкции в соответствии с видами вычисляемых значений.
Классификации систем типов:
- Наличие системы типов: есть/нет.
- Типизация статическая/динамическая.
- Типизация явная/неявная.
- Типизация сильная/слабая.
Наличие системы типов:
- Нет: язык ассемблера, язык FORTH, язык B (Би) — предшественник Си.
- Есть: все остальные языки.
Статическая и динамическая типизация:
- Статическая типизация — у каждой именованной сущности (переменной, функции…) есть свой фиксированный тип, он не меняется в процессе выполнения программы. Примеры: Си, C++, Java, Haskell, Rust, Go.
- Динамическая — тип переменной/функции известен только во время выполнения программы. Примеры: Scheme, JavaScript, Python.
Явная и неявная типизация:
- Явная — тип данных для сущностей явно записывается в программе. Например, int x в языке Си. Языки с явной типизацией: Си, C++, Java и т.д.
- Неявная — тип данных можно не указывать. Неявная типизация характерна прежде всего для динамически типизированных языков. В статически типизированных языках используется совместно с выводом типов. Вывод типов переменных присутствует в следующих языках: C++ (ключевое слово auto ), Go (когда тип переменной не указан), Rust, Haskell и т.д.
/* Язык Си, тип указывается явно */ int x = 100;
/* тип выводится компилятором */ auto x = 100; /* int */ auto y = "abc"; /* const char * */
var x int = 100 var y = 200 /* тип выведет компилятор */
- Сильная — неявные преобразования типов запрещены. Например, нельзя сложить строку и число. Языки с сильной типизацией: Scheme, Python, Haskell.
- Слабая типизация — неявные преобразования допустимы. Например, в JavaScript при сложении строки с числом число преобразуется в строку. Если в JavaScript в переменной лежит строка с последовательностью цифр, то, при умножении её на число, она неявно преобразуется в число: ‘1000’ * 5 → 5000 . Примеры языков: JavaScript, Си, Perl, PHP.