Почему идентификаторы не должны начинаться с цифры?
32
Похоже, что большинство языков программирования не позволяют объявлять идентификатор, начинающийся с цифры. Мне было просто интересно узнать причину. Я уже искал в Интернете, но не смог найти удовлетворительного объяснения.
В C / C ++ число, за которым следует буква, считается числовой константой, а следующая за ней строка определяет тип константы. Так, например (это VC ++, не уверен, насколько они стандартны):
0 - целое число со знаком
0l - длинное целое число со знаком
0u - целое число без знака
0i64 - 64-битное целое число со знаком
Итак, а) лексеру легче, как сказал Даниэль, но также и б) он делает явное различие, поскольку 0y может быть переменной, а 0u никогда не будет. Кроме того, другие квалификаторы, такие как «i64», были добавлены позже, чем «l» или «u», и они хотят оставить опцию открытой, добавляя больше, если это необходимо.
кроме того, шестнадцатеричные числа записываются в форме 0xd +, где d + - это еще 1 шестнадцатеричная цифра 0-f - так что 0xbeef является совершенно допустимым «числом».
tcrosley
20
вы, ребята, понимаете, что я не собираюсь говорить о спецификациях языка, а лишь предоставил несколько примеров, чтобы проиллюстрировать это, верно?
Я не думаю, что это правильное объяснение. Правило «идентификатор не может начинаться с цифры» справедливо для Algol, Pascal и других языков, в которых алфавитные суффиксы не допускаются для числовых констант.
Ларри Гриц,
1
@LarryGritz: «Последовательное разделение слов пробелами стало общим обычаем в десятом веке нашей эры и продолжалось до 1957 года, когда ФОРТРАН отказался от практики». —Солнение Справочное руководство по Фортрану (из вики). У Фортрана были свои особые причины, потому что они решили, что пробелы вообще необязательны. СОВРЕМЕННЫЕ языки любят свои пробелы. Ты сам по себе с Алголом, но я не такой уж современный. С другой стороны, все C / C ++ / C # / F # имеют суффиксы.
ДХМ
49
Удобство людей, реализующих лексер. (Нет, серьезно, это об этом. У разных языков есть другие причины, но в конечном итоге это сводится к тому.)
Было бы легко различить интегральные литералы и идентификаторы, начиная с цифр, используя PEG или другие современные методы синтаксического анализа. Даже компиляторы, использующие примитивные лексеры, могут поместить их в одну и ту же категорию токенов и дифференцировать позже. Было бы очень неловко, если бы, например, 0fluбыл литералом и 0gluбыл локальным идентификатором.
Даниэль Любаров
2
Люди абсолютно могут их различить. Решение принимается на основе удобства (или, если вы менее благотворительны, лень), а не технических требований.
Даниэль Питтман
2
@DanielPittman: Вам понадобится семантический анализ, чтобы сделать какое-либо надежное устранение неоднозначности, так что это не может быть сделано в лексере. Выдвижение решения из лексера делает синтаксический анализатор более сложным, и в чем выгода? Помимо очень плохой ситуации с затратами и выгодами, просто не существует хорошего способа справиться с делом, подобным тому, как int 0u = 5; unsigned int x = 0u;вы. Однако вы решаете определить интерпретацию этого кода (вероятно, либо x == 0, либо x == 5), люди будут сбиты с толку из-за двусмысленности. Даже если бы было так легко реализовать компилятор, хороший дизайнер, скорее всего, этого не сделает.
Джорен
10
Главное удобство для парсера в моей голове, а не для создателя языка.
CodesInChaos
2
Многие все еще удивляются, узнав, что лексический анализ обычно является большим фактором самой медленной стадии компилятора / интерпретатора.
hippietrail
20
Рассмотрим следующие 2 случая:
Случай 1
Предположим, что идентификатор может начинаться с цифры.
Таким образом, утверждение, подобное приведенному ниже, будет допустимым (поскольку идентификатор может содержать 1 или более символов):
int 3;
Когда я пытаюсь использовать вышеуказанную переменную в программе, это приводит к неоднозначности компилятора:
int 3, a;
3 = 5;
а = 3;
В утверждении, a=3какова роль 3 (это переменная со значением 5 или цифра 3)?
Дело 2
В отличие от приведенного выше примера, давайте предположим, что язык должен был фактически разрешать идентификаторы, начинающиеся с цифры, но при этом запрещать использование цифр в качестве идентификаторов. Это может вызвать следующие проблемы:
Языковые правила, касающиеся переменной, которая гласит, что переменная может состоять из 1 или более символов, должны быть переопределены в сложное правило, например: переменная может иметь один или несколько символов и должна быть уникальной, если она не начинается с цифры, а он не может иметь длину одного символа при запуске с числа (и т. д.)
Компилятору придется проверять и сообщать о случаях ошибок, когда в качестве имен переменных используются все цифры (например, 333) и действительные суффиксы алфавита (например, 34L). В слабо типизированных языках, таких как Python и JS, где вы можете использовать переменные на лету, не объявляя их, может даже оказаться невозможным проверить специальные случаи, включающие все цифры, например, if (33==5)здесь 33 может быть ошибочной необъявленной переменной, которую объявил пользователь. Но компилятор не сможет определить это и сообщить об ошибке.
Это ограничение не позволит программисту использовать числа в качестве имен идентификаторов.
Согласно этой логике, идентификаторы не могут содержать символы, так как они будут неоднозначными для ключевых слов. Можете ли вы представить, насколько катастрофическим int char = floatбудет?
Pubby
4
@Pubby: я не понимаю, как вы можете экстраполировать то, что я сказал, в какой-то совершенно бессмысленный смысл, который я пока не могу понять. Что означает ваш комментарий?
aml90
Я говорю, что вы воспринимаете вопрос слишком буквально и что это вовсе не двусмысленно, используя лексический приоритет. Например, как компилятор узнает, intчто это ключевое слово, а не идентификатор? Ну, intимеет более высокий приоритет, как и числовые лексемы.
Pubby
@Pubby: Под неоднозначностью я подразумевал, что компилятор не будет знать, в каком контексте я использую имя переменной (даже используя лексический приоритет). Например, рассмотрим этот код: int 3,a; 3=5; a=3; В выражении a = 3 интерпретируется ли 3 как идентификатор или как число? Это вызывает двусмысленность. Надеюсь, это понятно.
aml90
2
Я также считаю этот аргумент слабым. Было бы тривиально написать лексер, который бы принимал идентификаторы, которые начинаются, но не полностью состоят из цифр.
Ларри Гриц,
11
По большей части это не имеет ничего общего с упрощением работы компиляторов и эффективностью синтаксического анализа, но в большей степени связано с разработкой синтаксиса, обеспечивающего четкий читаемый и однозначный код.
Разработчики языка решили, что было бы неплохо иметь возможность писать числовые литералы, такие как число 1, как просто 1 .
Было бы вполне возможно разработать синтаксис языка, в котором числовые литералы каким-либо образом заключались в кавычки, например, в tildas, поэтому числовой литерал для номера один был закодирован как ~ 1 ~, а все, что не являлось ключевым словом и не заключалось в кавычки, обрабатывалось как имя переменной ,
Таким образом, вы могли бы кодировать операторы вроде:
1 = ~2~
two = 1 * ~2~
Но и:
2 = ~3~
six = 2 + 2
Какой бы синтаксис вы ни выбрали, двусмысленный и сложный для понимания код неизбежен.
Язык C и большинство языков "фигурных скобок", происходящих от C, также считают хорошей идеей позволить программистам непосредственно кодировать литералы Octal и Hexadecimal, а также указывать тип литерала, если это важно. Так
010 // Octal 10 = 8;
0x10 // Hexadecimal 10 = 16;
5l // long integer with decimal value 5
2.0d // double float with value 2
Таким образом, даже если вы позволите именам переменных начинаться с цифры, за которой следует комбинация цифр и букв, включающая хотя бы одну букву, вы поставите перед программистом задачу решить, будет ли данная группа формировать имя переменной или числовой литерал, так
2lll = 22 // OK
2ll = 2 // compiler error
Такая двусмысленность не помогла бы никому писать или читать программу.
Для тесно связанного примера из реального мира вы можете взглянуть на язык PL / 1, дизайнеры которого считали, что возможность использовать ключевые слова в качестве имен переменных была хорошей идеей, чтобы:
IF THEN THEN THEN = ELSE; ELSE ELSE = THEN;
IF IF THEN ELSE = IF; ELSE THEN = ELSE;
DO WHILE (WHILE = DO); END = WHILE + DO; END;
Действительный код, который компилирует и выполняет.
C был разработан как портативная сборка для Unix. Изначально Unix был спроектирован для 18-битной машины, где восьмеричное значение хорошо подходит для печати, а шестнадцатеричное - для печати 8/16/32-битных значений. Следовательно, они действительно нуждались в восьмеричном.
Также для битовой перестановки (ИЛИ, XOR, И, НЕ) и реализации драйверов устройств важно указать точный размер литерала, а также значение!
Джеймс Андерсон
10
Fortran оказал огромное влияние на то, как создавались более поздние языки. Ранее (некоторые из этих проблем с тех пор были исправлены) у Фортрана почти не было правил, ограничивающих, какое имя вы можете дать идентификатору. Это сделало язык чрезвычайно сложным для анализа как для компиляторов, так и для программистов. Вот один классический пример:
if if .eq. then then = else else else = endif endif
K I K K I I K I I K
Здесь я пометил «ключевые слова языка» буквой K и идентификаторами (именами переменных) I. Учитывая, что в написании нет различий, я думаю, вы, вероятно, можете понять, насколько это может сбивать с толку. Конечно, это крайний пример, и вряд ли кто-то когда-либо писал такой код специально. Иногда люди делали «Recycle» язык ключевые слова в качестве имен идентификаторов , хотя - и в большинстве случаев просто опечатка может привести к коду , что язык спецификации указанной должен быть проанализирован таким образом, даже если она не предназначалась вовсе. Для другого известного примера сравните это:
do 10 i = 1,10
к этому:
do 10 i = 1.10
Первый - это цикл do - повторение блока кода 10 раз. Во втором, однако, запятая была изменена на десятичную точку, поэтому она присваивает значение 1.10переменной с именем do 10 i.
Это также означало, что написание синтаксического анализатора на Фортране было относительно сложно - вы не могли быть уверены, что doв начале строки действительно было ключевое слово, пока вы не достигли конца строки, и проверили, что все остальные элементы doпетли присутствовали. Обычно синтаксический анализатор должен был быть готов к «возврату», заново анализируя строку с самого начала, чтобы найти «правильный» (но часто непреднамеренный) ответ о том, что действительно было.
Через несколько лет разработчики языка (большинство из них в любом случае) пошли к противоположной крайности - максимально ограничивали почти все в языке, при этом пользователи не жаловались слишком сильно.
В раннем Бейсике, например, в основном говорилось, что вы даже не можете использовать ключевое слово в качестве части идентификатора - например, fora=1будет анализироваться как for a = 1(т. Е. Начало forцикла, а не присвоение). Это, очевидно, вызвало достаточно жалоб, что это длилось не очень долго. Правило о начале идентификатора с цифрой, по-видимому, не вызвало большого количества жалоб, поэтому оно продолжает использоваться (по крайней мере, в большинстве языков).
ИМХО это ближе всего к реальной причине. Ранние языки, такие как Fortran, были, в некотором смысле, слишком неструктурированными, что приводило к трудностям при написании надежных компиляторов и затруднению для людей для правильного визуального анализа исходного кода. "Do10i = ..." - классический и известный пример. По мере развития языков некоторые правила были ужесточены. Алгол, вероятно, является дедушкой стандартного правила «идентификаторы начинаются с букв, а после этого могут иметь буквы или цифры».
Ларри Гриц
FYI, интерпретатор Microsoft BASIC, который лег в основу самых популярных версий BASIC для микрокомпьютеров (включая Applesoft Basic и Commodore Basic), использовал жадный токенизатор для преобразования любой последовательности символов, сопоставляющих языковой токен, в значение байта с установленным старшим битом. Это было сделано без какого-либо синтаксического анализа. Затем при запуске программы интерпретатор принимает любые найденные буквы как часть имени переменной.
суперкат
1
Вероятно, это соглашение возникло из очень ранних исторических решений по проектированию языка, поскольку на ранних машинах весь компилятор, включая лексический анализ, должен был работать за несколько киловатт-часов, меньше памяти, чем даже просто кэш данных процессора первого уровня на современных мобильных устройствах, поэтому допустимые имена переменных были очень ограничены, и их было легко отличить от числовых констант в очень немногих кодах операций.
Таким образом, соглашение стало тем, к чему привыкли поколения программистов.
Это не логически обязательное правило для языка программирования, а просто соглашение, используемое многими разработчиками языка.
Я могу разработать совершенно другой язык, который позволяет использовать все символы для идентификаторов. Для всех строк кода первые 20 символов будут описывать тип оператора, затем следующие 20 символов будут определять первый символ для оператора, а следующие 20 символов являются операндами для оператора. Этот язык будет выполняться на процессоре стека.
01234567890123456789 01234567890123456789 01234567890123456789
decl symbol 12345
assign value 12345 12345
decl symbol 99999
assign value 99999 12345
push 12345
push 99999
add
print top
Этот код может быть переведен на C, как показано ниже:
int i12345 = 12345;
int i99999 = 12345;
printf("%d", i12345+i9999);
Вот и все. Это бессмысленно, и правило «без числа в идентификаторах» также бессмысленно с логической точки зрения.
В дополнение к «удобству для лексера», я думаю, стоит также рассмотреть «удобство для читателя».
При чтении кода необходимо быстро и многократно определить, какие слова являются идентификаторами, а какие - числами. Поиск цифры в начале легче в нашем визуальном сопоставлении с образцом; это было бы рутиной, если бы нам нужно было тщательно проверить всех персонажей, чтобы убедиться.
Ответ на этот вопрос лежит в автоматах или, точнее, в конечных автоматах, определяющих регулярное выражение. Правило ... компиляторам нужны точные алгоритмы или правила для определения каждого символа, который они анализируют. Если идентификаторы были разрешены для начала с числа, то компилятор будет исправлен ... о характере приходящего токена ... будет ли это число или идентификатор ... и так как компиляторы не смогут вернуться к более ранним позициям .. .so .. чтобы дать понять компилятору, что приходящий токен является в точности идентификатором или числом ... это ограничение есть ... потому что это ... компилятор знает, просто сканируя первый символ, который входит в токен является идентификатором или номером.
Ответы:
В C / C ++ число, за которым следует буква, считается числовой константой, а следующая за ней строка определяет тип константы. Так, например (это VC ++, не уверен, насколько они стандартны):
Итак, а) лексеру легче, как сказал Даниэль, но также и б) он делает явное различие, поскольку 0y может быть переменной, а 0u никогда не будет. Кроме того, другие квалификаторы, такие как «i64», были добавлены позже, чем «l» или «u», и они хотят оставить опцию открытой, добавляя больше, если это необходимо.
источник
Удобство людей, реализующих лексер. (Нет, серьезно, это об этом. У разных языков есть другие причины, но в конечном итоге это сводится к тому.)
источник
0flu
был литералом и0glu
был локальным идентификатором.int 0u = 5; unsigned int x = 0u;
вы. Однако вы решаете определить интерпретацию этого кода (вероятно, либо x == 0, либо x == 5), люди будут сбиты с толку из-за двусмысленности. Даже если бы было так легко реализовать компилятор, хороший дизайнер, скорее всего, этого не сделает.Рассмотрим следующие 2 случая:
Случай 1
Предположим, что идентификатор может начинаться с цифры.
Таким образом, утверждение, подобное приведенному ниже, будет допустимым (поскольку идентификатор может содержать 1 или более символов):
Когда я пытаюсь использовать вышеуказанную переменную в программе, это приводит к неоднозначности компилятора:
В утверждении,
a=3
какова роль 3 (это переменная со значением 5 или цифра 3)?Дело 2
В отличие от приведенного выше примера, давайте предположим, что язык должен был фактически разрешать идентификаторы, начинающиеся с цифры, но при этом запрещать использование цифр в качестве идентификаторов. Это может вызвать следующие проблемы:
Языковые правила, касающиеся переменной, которая гласит, что переменная может состоять из 1 или более символов, должны быть переопределены в сложное правило, например: переменная может иметь один или несколько символов и должна быть уникальной, если она не начинается с цифры, а он не может иметь длину одного символа при запуске с числа (и т. д.)
Компилятору придется проверять и сообщать о случаях ошибок, когда в качестве имен переменных используются все цифры (например, 333) и действительные суффиксы алфавита (например, 34L). В слабо типизированных языках, таких как Python и JS, где вы можете использовать переменные на лету, не объявляя их, может даже оказаться невозможным проверить специальные случаи, включающие все цифры, например,
if (33==5)
здесь 33 может быть ошибочной необъявленной переменной, которую объявил пользователь. Но компилятор не сможет определить это и сообщить об ошибке.Это ограничение не позволит программисту использовать числа в качестве имен идентификаторов.
источник
int char = float
будет?int
что это ключевое слово, а не идентификатор? Ну,int
имеет более высокий приоритет, как и числовые лексемы.int 3,a; 3=5; a=3;
В выражении a = 3 интерпретируется ли 3 как идентификатор или как число? Это вызывает двусмысленность. Надеюсь, это понятно.По большей части это не имеет ничего общего с упрощением работы компиляторов и эффективностью синтаксического анализа, но в большей степени связано с разработкой синтаксиса, обеспечивающего четкий читаемый и однозначный код.
Разработчики языка решили, что было бы неплохо иметь возможность писать числовые литералы, такие как число 1, как просто 1 .
Было бы вполне возможно разработать синтаксис языка, в котором числовые литералы каким-либо образом заключались в кавычки, например, в tildas, поэтому числовой литерал для номера один был закодирован как ~ 1 ~, а все, что не являлось ключевым словом и не заключалось в кавычки, обрабатывалось как имя переменной ,
Таким образом, вы могли бы кодировать операторы вроде:
Но и:
Какой бы синтаксис вы ни выбрали, двусмысленный и сложный для понимания код неизбежен.
Язык C и большинство языков "фигурных скобок", происходящих от C, также считают хорошей идеей позволить программистам непосредственно кодировать литералы Octal и Hexadecimal, а также указывать тип литерала, если это важно. Так
Таким образом, даже если вы позволите именам переменных начинаться с цифры, за которой следует комбинация цифр и букв, включающая хотя бы одну букву, вы поставите перед программистом задачу решить, будет ли данная группа формировать имя переменной или числовой литерал, так
Такая двусмысленность не помогла бы никому писать или читать программу.
Для тесно связанного примера из реального мира вы можете взглянуть на язык PL / 1, дизайнеры которого считали, что возможность использовать ключевые слова в качестве имен переменных была хорошей идеей, чтобы:
Действительный код, который компилирует и выполняет.
источник
Fortran оказал огромное влияние на то, как создавались более поздние языки. Ранее (некоторые из этих проблем с тех пор были исправлены) у Фортрана почти не было правил, ограничивающих, какое имя вы можете дать идентификатору. Это сделало язык чрезвычайно сложным для анализа как для компиляторов, так и для программистов. Вот один классический пример:
Здесь я пометил «ключевые слова языка» буквой K и идентификаторами (именами переменных) I. Учитывая, что в написании нет различий, я думаю, вы, вероятно, можете понять, насколько это может сбивать с толку. Конечно, это крайний пример, и вряд ли кто-то когда-либо писал такой код специально. Иногда люди делали «Recycle» язык ключевые слова в качестве имен идентификаторов , хотя - и в большинстве случаев просто опечатка может привести к коду , что язык спецификации указанной должен быть проанализирован таким образом, даже если она не предназначалась вовсе. Для другого известного примера сравните это:
к этому:
Первый - это цикл do - повторение блока кода 10 раз. Во втором, однако, запятая была изменена на десятичную точку, поэтому она присваивает значение
1.10
переменной с именемdo 10 i
.Это также означало, что написание синтаксического анализатора на Фортране было относительно сложно - вы не могли быть уверены, что
do
в начале строки действительно было ключевое слово, пока вы не достигли конца строки, и проверили, что все остальные элементыdo
петли присутствовали. Обычно синтаксический анализатор должен был быть готов к «возврату», заново анализируя строку с самого начала, чтобы найти «правильный» (но часто непреднамеренный) ответ о том, что действительно было.Через несколько лет разработчики языка (большинство из них в любом случае) пошли к противоположной крайности - максимально ограничивали почти все в языке, при этом пользователи не жаловались слишком сильно.
В раннем Бейсике, например, в основном говорилось, что вы даже не можете использовать ключевое слово в качестве части идентификатора - например,
fora=1
будет анализироваться какfor a = 1
(т. Е. Началоfor
цикла, а не присвоение). Это, очевидно, вызвало достаточно жалоб, что это длилось не очень долго. Правило о начале идентификатора с цифрой, по-видимому, не вызвало большого количества жалоб, поэтому оно продолжает использоваться (по крайней мере, в большинстве языков).источник
Вероятно, это соглашение возникло из очень ранних исторических решений по проектированию языка, поскольку на ранних машинах весь компилятор, включая лексический анализ, должен был работать за несколько киловатт-часов, меньше памяти, чем даже просто кэш данных процессора первого уровня на современных мобильных устройствах, поэтому допустимые имена переменных были очень ограничены, и их было легко отличить от числовых констант в очень немногих кодах операций.
Таким образом, соглашение стало тем, к чему привыкли поколения программистов.
источник
Это не логически обязательное правило для языка программирования, а просто соглашение, используемое многими разработчиками языка.
Я могу разработать совершенно другой язык, который позволяет использовать все символы для идентификаторов. Для всех строк кода первые 20 символов будут описывать тип оператора, затем следующие 20 символов будут определять первый символ для оператора, а следующие 20 символов являются операндами для оператора. Этот язык будет выполняться на процессоре стека.
Этот код может быть переведен на C, как показано ниже:
Вот и все. Это бессмысленно, и правило «без числа в идентификаторах» также бессмысленно с логической точки зрения.
источник
В дополнение к «удобству для лексера», я думаю, стоит также рассмотреть «удобство для читателя».
При чтении кода необходимо быстро и многократно определить, какие слова являются идентификаторами, а какие - числами. Поиск цифры в начале легче в нашем визуальном сопоставлении с образцом; это было бы рутиной, если бы нам нужно было тщательно проверить всех персонажей, чтобы убедиться.
источник
Ответ на этот вопрос лежит в автоматах или, точнее, в конечных автоматах, определяющих регулярное выражение. Правило ... компиляторам нужны точные алгоритмы или правила для определения каждого символа, который они анализируют. Если идентификаторы были разрешены для начала с числа, то компилятор будет исправлен ... о характере приходящего токена ... будет ли это число или идентификатор ... и так как компиляторы не смогут вернуться к более ранним позициям .. .so .. чтобы дать понять компилятору, что приходящий токен является в точности идентификатором или числом ... это ограничение есть ... потому что это ... компилятор знает, просто сканируя первый символ, который входит в токен является идентификатором или номером.
источник