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

Ассемблер как единственный нетипизированный язык

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

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

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

Рассмотрим примеры

a)

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

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

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

б)

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

Обратите внимание, число 42 помещается в одну ячейку памяти. Если мы захотим написать программу, которая склеит “привет” и “42” в “привет42”, нам потребуется сначала разбить 42 на несколько ячеек памяти, каждая из которых будет содержать порядковый номер символа текущей цифры. Эта операция называется приведение типов

Иначе, могло бы получиться нечто похожее на пример выше. Я сложил 42 с порядковыми номерами букв (символов) в русском алфавите)

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

Языки со статической типизацией

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

Если обобщить, существует три базовых типа данных (примитивов): строки, boolean и числа. Строгая типизация данных подразумевает объявление типа ячейки памяти заранее. Рассмотрим на примере кода на языке программирования Си.

Данный пример исходного кода складывает числа 2, 2 и выводит четыре. Объявление типа ячейки осуществляется заранее с использованием слова int.

Аналогичный код объявляет строку до 10 символов, осуществляет вывод на экран. Однако, конструкция ниже приведет к ошибкам исполнения. Это связано с вышеупомянутой проблемой приведения типа. Данный язык программирования создавался, в том числе, для обеспечения командной работы, поэтому, ограничивает программиста от подобных неявных преобразований

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

Языки с динамической типизацией

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

Языки с динамической типизацией вычисляют тип ячейки памяти по значению внутри, что позволяет уменьшить размер кода. К таким языкам относятся JavaScript, Python, Ruby, Lua и другие.

Рассмотрим пример кода на Си, осуществляющий заполнение ранее упомянутой таблицы

Тот же код, написанный на языке программирования JavaScript (динамически типизированный язык) займет значительно меньше строк, рассмотрим пример ниже

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

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

О TypeScript

Язык программирования TypeScript повторил судьбу языка Си и Ассемблера. Изначально спроектированный с динамической типизацией JavaScript подразумевал, что он будет использоваться для мелкой работы на вебстранице, с которой вполне справится один разработчик. Однако, в последующем, так как в развитие интернета были вложены огромные суммы денег, язык стал преуспевать и на нем начали вести командную работу

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

Как следствие, если вы собираетесь разрабатывать приложение самостоятельно, возможно, вам стоит посмотреть в сторону JavaScript для уменьшения размера кода, но, если вы собираетесь работать в команде TypeScript - ваш выбор.

Комментарии (9)


  1. tripolskypetr Автор
    01.06.2022 18:00
    -4

    Будем считать, что это закладка от републикации)


  1. aleksandy
    01.06.2022 11:14
    +6

    Данный пример исходного кода складывает числа 2, 2 и выводит четыре.

    Нет, sum никак не проинициализировано.

    Аналогичный код объявляет строку до 10 символов, осуществляет вывод на экран.

    Опять таки нет, строка - firstName. Выводим - number1. Тут вообще ошибка компиляции.

    P.S. Что за маниакальная тяга выкладывать код картинками? Ну есть же в редакторе специальный код для этого.


    1. APXEOLOG
      01.06.2022 12:01
      +2

      P.S. Что за маниакальная тяга выкладывать код картинками? Ну есть же в редакторе специальный код для этого.

      Видимо чтобы читателям было лень проверять и находить ошибки


    1. tripolskypetr Автор
      01.06.2022 18:00
      -4

      Будем считать, что это закладка от републикации)


  1. SergeiMinaev
    01.06.2022 22:20

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

    Зато в том же статически, но слабо-типизированном Си (и в С++, если не путаю, тоже) такой код gcc скомпилит даже без варнингов и в c будет единичка:

    #include <stdbool.h>
    #include <stdio.h>

    int main() {
    bool a = true;
    int b = 2;
    int c = b - a; // c == 1
    printf("r: %d\n", c);
    }

    Правда, в строго-типизированном Python это тоже сработает:
    >>> 2 - True
    1

    Тут, конечно, дело в том, что булы в Си под капотом являются интами. Rust уже не даст так делать.


    1. Vitya_Nikolayev
      02.06.2022 09:45

      Булы в си (начиная с C99) под капотом являются однобайтовыми _Bool, а при конвертации из других типов в _Bool конструируется сравнение с 0 (в x86 во что-то типа такого: cmp [var],0; jne $+Eh; mov [boolVar],0; jmp $+Ch; mov [boolVar],1;, на плюсах bool зарезервированное ключевое слово и там тоже при конвертации на неё создаётся подобная конструкция), а bool из stdbool.h это макрос, который разворачивается в _Bool, true и false макросы 1 и 0 соответственно


  1. Tresimeno
    02.06.2022 09:14

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


  1. Kardfox
    02.06.2022 09:14

    Если обобщить, существует три базовых типа данных (примитивов): строки, boolean и числа

    Разве строки были в С? Строк, как таковых не было, были лишь массивы char


    1. Vitya_Nikolayev
      02.06.2022 10:58

      Да, строки не примитивы, даже в современных яп они комплексные структуры. Имхо, в принципе примитивы только целые числа разной байтности, даже бульки с некоторым оверхедом в машинном коде (по крайней мере в x86 и в x64 точно), ну ещё и числа с плавающей точкой стандарта IEEE 754 (в современных компьютерах)


  1. Vitya_Nikolayev
    02.06.2022 10:37

    Да кто ж числа, используемые для арифметических вычислений, в строках хранит?