Практически все языки программирования строятся либо по принципу подобия (делать похожим на этот, только со своим собственным блэкджеком), либо для реализации какой-либо новой концепции (модульность, чистота функциональных вычислений и т. д.). Или и то, и другое одновременно.
Но в любом случае, создатель нового языка программирования не берет свои идеи наобум. Они по-прежнему основаны на его предыдущем опыте, увлеченности новой концепцией и других первоначальных настройках и ограничениях.
Существует ли минимальный набор лексем, операторов или синтаксических конструкций, которые можно использовать для построения произвольной грамматики для современного универсального языка программирования?
Сразу признаюсь, что не могу однозначно перечислить минимальный набор базовых операторов и конструкций, достаточных для современного языка программирования. Более того, я не уверен, что такой набор вообще возможен, поскольку многие конструкции могут быть представлены с помощью других, более низкоуровневых конструкций (например, условный/безусловный переход). Я помню о машине Тьюринга, но меня интересуют реальные языки программирования, а не машинные инструкции на абстрактном исполнителе.
Поэтому, в качестве основных строительных блоков языков программирования мы можем смело принять те особенности, которые были изобретены и реализованы разработчиками распространенных языков. И, вероятно, лучше начать с критики отдельных и хорошо известных фундаментальных концепций. И нет, это не оператор goto!
На мой взгляд, наиболее однозначными операторами являются операторы инкремента и декремента, то есть арифметического увеличения или уменьшения значения переменной на единицу. Они вызывают серьёзную путаницу в строгой грамматике языка, которая, по моему мнению, должна быть максимально прозрачной и неоднозначной .
Основная проблема этих операторов заключается в том, что, будучи арифметическими операторами, они изменяют значение переменной, тогда как все остальные арифметические операторы работают с копиями значений, не изменяя непосредственно саму переменную.
Я могу возразить, что операторы +=, -=, *= или \= также изменяют значение переменной, но хотел бы отметить, что это лишь упрощенное обозначение комбинации двух операторов, один из которых предназначен для присвоения переменной нового значения, поэтому возражения не принимаются.
А если вспомнить, что операторы инкремента и декремента могут быть префиксными и постфиксными, то в сочетании с адресной арифметикой (*val++ или какой-либо ++*val++) взрыв мозга с возможными ошибками просто гарантирован.
Да, вы всё правильно прочитали! Я критикую оператор присваивания одного значения « = », потому что считаю его не совсем полным. Но в отличие от инкремента и декремента, без которых язык программирования легко может обойтись, без оператора присваивания обойтись невозможно!
Но моя критика направлена не на сам оператор, а на его неполноту и создание дополнительной путаницы в некоторых языках программирования. Например, в одном и том же Python невозможно понять, создается ли переменная (то есть первое использование переменной) или присваивается значение уже существующей переменной (или же программист допустил опечатку в имени переменной).
Если помнить поговорку «если критикуешь, предлагай», то было бы правильно использовать два разных оператора: оператор присваивания значения и оператор создания переменной (в C/C++ логика создания переменной выполняется путем указания ее типа при первом использовании).
Другими словами, вместо одного оператора «создать и/или присвоить значение» лучше использовать два или даже три оператора: создание новой переменной ( ::= ), присвоение значения только уже существующей переменной ( = ) и создание/присвоение независимо от существования переменной ( := ) — то есть аналог текущего оператора = .
В этом случае компилятор мог бы контролировать создание или повторное использование ранее созданной переменной в соответствии с намерениями программиста уже на уровне исходного синтаксиса.
Также можно добавить оператор «обмена значениями», например, some :=: . По сути, это аналог std::swap() в C++, только на уровне синтаксиса языка.
Все языки массового программирования обычно содержат числа с разным количеством разрядов. Это обязательное условие, поскольку количество разрядов в вычислениях определяется аппаратным обеспечением, и разработчики языков не могут это игнорировать.
Ещё один момент — это логический (булев) тип данных. В описании одного языка я даже встретил такой:
Логическое значение 1 байт (истинное значение)
(Bool16) 2-байтовое истинностное значение
(Bool32) 4-байтовое значение истинности
(Bool64) 8-байтовое значение истинности
А если копнуть глубже, всё сводится к одному-единственному биту, который можно использовать для представления двух противоположных состояний: ДА/НЕТ, истина/ложь, 1/0...
Но позвольте мне сказать, если это 1 или 0, почему бы сразу не определить, что логический тип — это число с одной цифрой? (как это делается в LLVM!).
В конце концов, нет работы хуже, чем бессмысленная работа по преобразованию чисел в логические значения и наоборот:
Так или иначе, все распространенные языки программирования содержат тип данных, называемый ссылкой . А в некоторых языках ссылочные типы могут быть нескольких типов одновременно.
Однако наличие ссылочных типов данных вносит сразу несколько неопределенностей, таких как управление памятью и разделяемыми ресурсами. Кроме того, если присутствует адресная арифметика (явная или нет), сразу же возникает необходимость использовать специальное зарезервированное значение, называемое «нулевым указателем», NULL , nil , nullptr и т. д., в зависимости от языка.
Наличие такого значения вынуждает разработчиков языков значительно усложнять синтаксис и логику работы с указателями, контролируя явную/неявную возможность хранения нулевого указателя в ссылочной переменной.
Но если компилятор языка будет самостоятельно управлять ссылочными типами данных и разделяемыми ресурсами, то само понятие «нулевой указатель» станет ненужным и будет скрыто от программиста в деталях реализации.
Бывают ситуации, когда отсутствует системная переменная со значением результата последней операции. Что-то аналогичное символу $? в bash-скриптах, но на уровне исходного кода Python или C/C++.
Но я имею в виду не конкретную физическую переменную, а некий обобщенный идентификатор, содержащий результат последней операции. Псевдопеременную, управляемую компилятором языка. Другими словами, тип этой псевдопеременной меняется в зависимости от того, какая операция была последней.
Это могло бы упростить решение часто встречающихся задач, например, получение последнего значения после выхода из цикла.
Или же подобная псевдопеременная могла бы упростить синтаксис обработки исключений, где перехват реализуется на основе типов. Но при этом с типом перехватываемого исключения необходимо определить переменную, даже если она больше никак не используется.
Кроме того, иногда мне хотелось бы иметь возможность создавать чистые функции в C/C++ или Python, чтобы компилятор сам контролировал запрет доступа к глобальным переменным или нечистым функциям на уровне синтаксиса языка, и это проверялось бы во время компиляции.
И наконец, я хотел бы отметить, что в C++ очень не хватало пустой переменной « _ » (как в Python). Но, похоже, она была введена в последних предложениях стандарта, так что мы будем рады, если она появится начиная с C++26
)
При написании этой статьи я старался абстрагироваться и беспристрастно подойти к своему более чем тридцатилетнему опыту разработки, но не уверен, что мне это удалось, поэтому буду рад любым замечаниям и возражениям в комментариях.
Если не сложно, напишите в комментариях, какие особенности современных языков программирования, по вашему мнению, больше мешают, чем помогают, или наоборот, каких операторов/синтаксических конструкций вам не хватает.
Всегда интересно узнать, что ты упустил или забыл.
Но в любом случае, создатель нового языка программирования не берет свои идеи наобум. Они по-прежнему основаны на его предыдущем опыте, увлеченности новой концепцией и других первоначальных настройках и ограничениях.
Существует ли минимальный набор лексем, операторов или синтаксических конструкций, которые можно использовать для построения произвольной грамматики для современного универсального языка программирования?
Введение
Сразу признаюсь, что не могу однозначно перечислить минимальный набор базовых операторов и конструкций, достаточных для современного языка программирования. Более того, я не уверен, что такой набор вообще возможен, поскольку многие конструкции могут быть представлены с помощью других, более низкоуровневых конструкций (например, условный/безусловный переход). Я помню о машине Тьюринга, но меня интересуют реальные языки программирования, а не машинные инструкции на абстрактном исполнителе.
Поэтому, в качестве основных строительных блоков языков программирования мы можем смело принять те особенности, которые были изобретены и реализованы разработчиками распространенных языков. И, вероятно, лучше начать с критики отдельных и хорошо известных фундаментальных концепций. И нет, это не оператор goto!
Странное увеличение и уменьшение (++ и --).
На мой взгляд, наиболее однозначными операторами являются операторы инкремента и декремента, то есть арифметического увеличения или уменьшения значения переменной на единицу. Они вызывают серьёзную путаницу в строгой грамматике языка, которая, по моему мнению, должна быть максимально прозрачной и неоднозначной .
Основная проблема этих операторов заключается в том, что, будучи арифметическими операторами, они изменяют значение переменной, тогда как все остальные арифметические операторы работают с копиями значений, не изменяя непосредственно саму переменную.
Я могу возразить, что операторы +=, -=, *= или \= также изменяют значение переменной, но хотел бы отметить, что это лишь упрощенное обозначение комбинации двух операторов, один из которых предназначен для присвоения переменной нового значения, поэтому возражения не принимаются.
А если вспомнить, что операторы инкремента и декремента могут быть префиксными и постфиксными, то в сочетании с адресной арифметикой (*val++ или какой-либо ++*val++) взрыв мозга с возможными ошибками просто гарантирован.
Мало операторов присвоения значений
Да, вы всё правильно прочитали! Я критикую оператор присваивания одного значения « = », потому что считаю его не совсем полным. Но в отличие от инкремента и декремента, без которых язык программирования легко может обойтись, без оператора присваивания обойтись невозможно!
Но моя критика направлена не на сам оператор, а на его неполноту и создание дополнительной путаницы в некоторых языках программирования. Например, в одном и том же Python невозможно понять, создается ли переменная (то есть первое использование переменной) или присваивается значение уже существующей переменной (или же программист допустил опечатку в имени переменной).
Если помнить поговорку «если критикуешь, предлагай», то было бы правильно использовать два разных оператора: оператор присваивания значения и оператор создания переменной (в C/C++ логика создания переменной выполняется путем указания ее типа при первом использовании).
Другими словами, вместо одного оператора «создать и/или присвоить значение» лучше использовать два или даже три оператора: создание новой переменной ( ::= ), присвоение значения только уже существующей переменной ( = ) и создание/присвоение независимо от существования переменной ( := ) — то есть аналог текущего оператора = .
В этом случае компилятор мог бы контролировать создание или повторное использование ранее созданной переменной в соответствии с намерениями программиста уже на уровне исходного синтаксиса.
Также можно добавить оператор «обмена значениями», например, some :=: . По сути, это аналог std::swap() в C++, только на уровне синтаксиса языка.
Всегда дополнительный тип данных
Все языки массового программирования обычно содержат числа с разным количеством разрядов. Это обязательное условие, поскольку количество разрядов в вычислениях определяется аппаратным обеспечением, и разработчики языков не могут это игнорировать.
Ещё один момент — это логический (булев) тип данных. В описании одного языка я даже встретил такой:
Логическое значение 1 байт (истинное значение)
(Bool16) 2-байтовое истинностное значение
(Bool32) 4-байтовое значение истинности
(Bool64) 8-байтовое значение истинности
А если копнуть глубже, всё сводится к одному-единственному биту, который можно использовать для представления двух противоположных состояний: ДА/НЕТ, истина/ложь, 1/0...
Но позвольте мне сказать, если это 1 или 0, почему бы сразу не определить, что логический тип — это число с одной цифрой? (как это делается в LLVM!).
В конце концов, нет работы хуже, чем бессмысленная работа по преобразованию чисел в логические значения и наоборот:
Кроме того, в некоторых языках программирования, поддерживающих Empty/None, логический тип данных может вообще превратиться в проблему, например, в случае параметров функций по умолчанию, когда к логическому аргументу добавляется состояние «не задано». Но с точки зрения использования неинициализированных переменных это, по крайней мере, понятно и логически объяснимо.В Java существуют довольно строгие ограничения на тип данных boolean: значения boolean нельзя преобразовать в какой-либо другой тип данных, и наоборот. В частности, boolean не является целочисленным типом, и целочисленные значения нельзя использовать вместо значений boolean.
Нулевой указатель
Так или иначе, все распространенные языки программирования содержат тип данных, называемый ссылкой . А в некоторых языках ссылочные типы могут быть нескольких типов одновременно.
Однако наличие ссылочных типов данных вносит сразу несколько неопределенностей, таких как управление памятью и разделяемыми ресурсами. Кроме того, если присутствует адресная арифметика (явная или нет), сразу же возникает необходимость использовать специальное зарезервированное значение, называемое «нулевым указателем», NULL , nil , nullptr и т. д., в зависимости от языка.
Наличие такого значения вынуждает разработчиков языков значительно усложнять синтаксис и логику работы с указателями, контролируя явную/неявную возможность хранения нулевого указателя в ссылочной переменной.
Но если компилятор языка будет самостоятельно управлять ссылочными типами данных и разделяемыми ресурсами, то само понятие «нулевой указатель» станет ненужным и будет скрыто от программиста в деталях реализации.
Результат последней операции
Бывают ситуации, когда отсутствует системная переменная со значением результата последней операции. Что-то аналогичное символу $? в bash-скриптах, но на уровне исходного кода Python или C/C++.
Но я имею в виду не конкретную физическую переменную, а некий обобщенный идентификатор, содержащий результат последней операции. Псевдопеременную, управляемую компилятором языка. Другими словами, тип этой псевдопеременной меняется в зависимости от того, какая операция была последней.
Это могло бы упростить решение часто встречающихся задач, например, получение последнего значения после выхода из цикла.
Или же подобная псевдопеременная могла бы упростить синтаксис обработки исключений, где перехват реализуется на основе типов. Но при этом с типом перехватываемого исключения необходимо определить переменную, даже если она больше никак не используется.
Чистые функции
Кроме того, иногда мне хотелось бы иметь возможность создавать чистые функции в C/C++ или Python, чтобы компилятор сам контролировал запрет доступа к глобальным переменным или нечистым функциям на уровне синтаксиса языка, и это проверялось бы во время компиляции.
Пустое имя переменной
И наконец, я хотел бы отметить, что в C++ очень не хватало пустой переменной « _ » (как в Python). Но, похоже, она была введена в последних предложениях стандарта, так что мы будем рады, если она появится начиная с C++26
В заключение
При написании этой статьи я старался абстрагироваться и беспристрастно подойти к своему более чем тридцатилетнему опыту разработки, но не уверен, что мне это удалось, поэтому буду рад любым замечаниям и возражениям в комментариях.
Если не сложно, напишите в комментариях, какие особенности современных языков программирования, по вашему мнению, больше мешают, чем помогают, или наоборот, каких операторов/синтаксических конструкций вам не хватает.
Всегда интересно узнать, что ты упустил или забыл.