В статье "Почему не надо учить Питон" я привел пример записи одного и того же выражения в разных языках:
a = 5
a := 5;
$a = 5;
var a = 5;
int a = 5;
var a:Integer = 5;
a = 5 – это запись на языке Python, и она выглядит самой простой. Видимо, поэтому считается, что Питон хорошо подходит для новичков. Зачем тогда другие языки мучают новичков какими-то непонятными обозначениями?
Рассмотрим вот эти примеры:
var a = 5;
int a = 5;
var a:Integer = 5;
Почему перед a написано var?
var – это сокращенно variable, то есть переменная.
"var a = 5" это значит "создать переменную a и присвоить ей значение 5". Ключевое слово здесь "создать". Переменной раньше не было, и теперь она появилась, благодаря слову "var". Что значит "появилась"? В свободной памяти компьютера было выделено место для хранения этой переменной, и этому месту присвоили метку "a", чтобы знать, куда обращаться. До этого не было ни выделенного места в памяти, ни метки.
Переменные потому и называются переменными, что могут принимать разные значения. И значит, где-то дальше в программе переменная a может измениться:
a = 10;
Теперь в ней хранится не 5, а 10. Заметьте, что больше не нужно писать var. Переменная уже была создана. Известна область памяти, где она хранится, и известна метка, присвоенная этой области памяти, значит, мы просто можем сказать транслятору "запиши число 10 в ту область памяти, которая помечена как a".
Теперь сравним с Питоном. Что мы делаем, когда хотим создать переменную? Пишем:
a = 5
Что мы делаем, когда хотим изменить переменную? Пишем:
a = 10
Но обе записи одинаковы. Откуда Питон знает, когда нужно создавать переменную, а когда менять её? А он и не знает. Просто когда он видит такую запись, то сначала проверяет, есть ли у него уже созданная переменная с такой меткой? Если есть, то меняет её. Если нет, то создает её. Казалось бы, всё легко и просто. Но это может привести к печальным последствиям.
Что, если где-то в тексте программы в имени переменной сделана опечатка? Тогда вместо того, чтобы изменить нужную нам переменную, Питон создаст новую, ведь он заметит, что такой переменной раньше не существовало. И программа будет работать неправильно. А вы будете искать, почему она работает неправильно, если с виду всё вроде как правильно. Вот вам и "язык для новичков".
В языках, где переменные создаются явным образом, такая ситуация практически исключена. Если вы попытаетесь присвоить a = 10, не создав до этого a, то сразу же получите ошибку, которую не надо искать.
Перейдем к следующему примеру из другого языка,
int a = 5;
Почему теперь вместо var написано int?
int - это сокращенно integer, то есть "целый". В данном случае решаются сразу две задачи: во-первых, создается переменная, и во-вторых, указывается, что она будет целочисленного типа. Это значит, что в памяти для неё будет выделено столько байтов, сколько нужно для хранения целого числа (обычно это не менее 2 байт). Чтобы изменить её, вы точно так же будете писать:
a = 10;
Но теперь у вас есть ещё одна защита от ошибок. Вы не можете присвоить этой переменной что-то выходящее за рамки целого числа, например очень большое число, дробное число или строку. Вы теперь и для этого случая получите ошибку, которую сразу будет видно.
Языки, в которых не нужно объявлять тип переменной, позволяют присвоить и переприсвоить любой переменной данные любого типа. Но какова цена такого подхода? Если для целого типа точно известно, что нужно выделить 2-4 байта памяти, то для неопределенного типа язык определяет его в момент перезаписи переменной. Если написано a = 5, то он видит, что 5 это целое число, и тогда он делает переменную целого типа. Но если потом разделить значение этой переменной на 2, то получится 2.5 – дробное число. Значит, после этого переменная должна стать вещественного типа. А если присвоим потом строку? Переменная станет строкового типа.
Поэтому в памяти вместе с переменной должна храниться ещё служебная информация: какого типа переменная сейчас? Где хранятся её данные? Ведь при каждом присваивании новых данных может быть невозможно вписать их в то же место, где были старые, если они другого типа. Значит, каждый раз выделяется новая память, а старая освобождается, и в информацию о переменной вносятся изменения, чтобы она соответствовала новому типу. Всё это скрыто от вас за простыми и легкими строчками кода, но внутри компьютера производится большая работа и, как следствие, программа работает медленнее и требует больше памяти.
Рассмотрим последний пример,
var a:Integer = 5;
Это гибрид первых двух вариантов, "var a = 5" и "int a = 5". Но как мы видели, чтобы создать переменную и одновременно назначить ей тип, достаточно было бы написать просто "int a" или "Integer a". Зачем ещё и var? Подобная запись довольно редко встречается (в моём примере это уже полумертвый язык ActionScript 3). Но всякий раз, когда вы видите похожее сочетание, можно предположить, что язык изначально создавался без типов, а потом в него добавили типы как необходимость. Так как уже нельзя было убирать var (иначе сломаются старые версии языка), то добавили Integer другим способом.
Аналогично будут выглядеть и объявления функций:
function hello(name)
String hello(String name)
function hello(name:String):String
и на десерт, Питон:
def hello(name: str) -> str:
Вам все ещё кажется, что это легкий язык?
Такие случаи выглядят довольно смешно. Сначала изобретается какой-то "легкий" язык, чтобы не писать в нём ничего лишнего, потом приходит понимание, что одной "легкости" недостаточно, и начинают прикручивать какие-то костыли, чтобы решить проблему. Нет легких языков! Вы решаете задачи, у задач есть сложность, и эту сложность нельзя понизить или повысить с помощью языка. Можно "заполировать" любое место, но сложность вылезет в другом.
Краткие выводы из этого материала: если язык предоставляет возможность использовать типы хоть в каком виде, используйте их. Вам придется тратить больше времени при написании кода, зато вы можете существенно сэкономить время при поиске ошибок. А также ускорить работу программы и сократить объем используемой памяти.