Por que os identificadores não devem começar com um número?


32

A maioria das linguagens de programação parece ter sido projetada para não permitir que se declare um identificador que começa com um número. Eu só estava curioso para saber o motivo. Já pesquisei na web, mas não consegui encontrar uma explicação satisfatória.


4
Você tem um exemplo único de um nome de variável em que traria um benefício por clareza e legibilidade?
Secure

5
@Secure: 3dspline, 4seasonPizza, 2pdfConverter, 8bitInt, ...
usuário desconhecido

6
Adiante permite. Dos incorporados: 2DUP, 2DROP, 2SWAP, 2> R, 2R @, 2R>, 0 =, etc.
Peter Mortensen

assim como o TCL, mas não acho que nenhum dos comandos padrão do TCL comece com um número
jk.

Respostas:


51

Em C / C ++, um número seguido por uma letra é considerado uma constante numérica e a sequência a seguir qualifica o tipo da constante. Então, por exemplo (estes são VC ++, não tenho certeza de como são padrão):

  • 0 - inteiro assinado
  • 0l - inteiro longo assinado
  • 0u - número inteiro não assinado
  • 0i64 - inteiro assinado de 64 bits

Portanto, a) é mais fácil para o lexer, como Daniel disse, mas também b) faz uma distinção explícita, pois 0y pode ser uma variável, mas 0u nunca seria. Além disso, outros qualificadores, como "i64", foram adicionados muito mais tarde que "l" ou "u" e desejam manter a opção de adicionar mais, se necessário.


7
além disso, os números hexadecimais são escritos no formato 0xd +, em que d + é mais 1 dígito hexadecimal 0-f - então 0xbeef é um "número" perfeitamente válido.
Tcrosley

20
vocês percebem que eu não estava indo para uma especificação de idioma, mas apenas fornecemos alguns exemplos para ilustrar o assunto, certo?
DXM

6
Re: "eles querem manter a opção aberta de adicionar mais, se necessário": E o C ++ 11 ainda permite que você adicione o seu; consulte http://en.wikipedia.org/wiki/C++11#User-defined_literals .
Ruakh

2
Não acho que essa seja a explicação correta. A regra "identificador não pode começar com um dígito" era verdadeira para Algol, Pascal e outros idiomas que não permitiam sufixos alfabéticos para constantes numéricas.
Larry Gritz

1
@LarryGritz: "Separar consistentemente palavras por espaços tornou-se um costume geral por volta do século X dC e durou até cerca de 1957, quando FORTRAN abandonou a prática". - Manual de Referência do Sun FORTRAN (do wiki). Fortran tinha seus próprios motivos especiais, porque eles decidiram que os espaços em geral eram opcionais. Línguas modernas como seus espaços em branco. Você está por sua conta com Algol, mas também não sou moderno. Por outro lado, C / C ++ / C # / F # todos têm sufixos.
DXM

49

A conveniência das pessoas que implementam o lexer. (Não, sério, é isso. Várias línguas têm outros motivos, mas, no final, tudo se resume a isso.)


2
Seria fácil distinguir entre literais integrais e identificadores começando com dígitos usando PEGs ou outras técnicas modernas de análise. Até os compiladores que usam lexers primitivos poderiam colocá-los na mesma categoria de token e diferenciar posteriormente. Seria muito estranho se, por exemplo, 0flufosse um literal e 0gluum identificador local.
Daniel Lubarov

2
É absolutamente possível que as pessoas os distingam. A decisão é tomada com base na conveniência (ou, se você for menos caridoso, na preguiça) e não nos requisitos técnicos.
Daniel Pittman

2
@ DanielPittman: Você precisaria de análise semântica para fazer qualquer tipo de desambiguação confiável, para que isso não possa ser feito no lexer. Adiar a decisão do lexer torna o analisador mais complexo e com que benefício? Além da péssima situação de custo / benefício, simplesmente não há uma boa maneira de lidar com um caso como int 0u = 5; unsigned int x = 0u;No entanto, você escolhe definir a interpretação desse código (provavelmente x == 0 ou x == 5), as pessoas ficarão confusas por causa da ambiguidade. Mesmo que fosse trivial implementar o compilador dessa maneira, um bom designer provavelmente não o faria.
Joren

10
A principal conveniência é para o analisador na minha cabeça, e não para o criador da linguagem.
CodesInChaos

2
Ainda é uma surpresa para muitas pessoas saber que a análise lexical geralmente é um fator importante no estágio mais lento de um compilador / intérprete.
Hippietrail

20

Considere os 2 casos a seguir:

Caso 1

Vamos supor que um identificador possa começar com um número.

Portanto, uma declaração como a seguir seria válida (já que um identificador pode ter 1 ou mais caracteres):

int 3;

Quando tento usar a variável acima em um programa, isso resulta em ambiguidade do compilador:

int 3, a;
3 = 5;
a = 3;

Na declaração, a=3qual é o papel de 3 (é uma variável com valor 5 ou é o número 3)?

Caso 2

Ao contrário do exemplo acima, vamos supor que um idioma realmente permita identificadores iniciando com um número, enquanto não permite que numerais sejam usados ​​como identificadores. Isso pode causar os seguintes problemas:

  • As regras de linguagem relativas à variável que diz que uma variável pode consistir em 1 ou mais caracteres terão que ser redefinidas para uma regra complexa como: Uma variável pode ter um ou mais caracteres e deve ser única se não começar com um número enquanto não pode ter comprimento de caractere único ao iniciar com um número (etc.)

  • O compilador precisará verificar e relatar casos de erro quando todos os números (por exemplo, 333) e sufixos de alfabeto válidos (por exemplo, 34L) estiverem sendo usados ​​como nomes de variáveis. Em linguagens de tipos vagos, como Python e JS, nas quais você pode usar variáveis ​​em tempo real sem declará-las, pode até ser impossível verificar os casos especiais que envolvem todos os números, por exemplo: if (33==5)Aqui, 33 pode ser uma variável não declarada errônea que o usuário declarou. Mas o compilador não poderá identificar isso e relatar o erro.

Fazer essa restrição impedirá que o programador use números como nomes de identificador.


2
Sob essa lógica, os identificadores não poderiam conter caracteres, pois seriam ambíguos às palavras-chave. Você pode imaginar como int char = floatseria desastroso ?
Pubby

4
@Pubby: Eu não vejo como você pode extrapolar o que eu disse para um total absurdo que ainda não consigo descobrir. O que seu comentário significa?
aml90

Estou dizendo que você está levando a pergunta muito literalmente e que não é nada ambígua usando precedência lexing. Por exemplo, como o compilador sabe que inté uma palavra-chave e não um identificador? Bem, inttem maior precedência, assim como lexemes numéricos.
Pubby

@Pubby: Por ambiguidade, eu quis dizer que o compilador não saberia em que contexto estou usando o nome da variável (mesmo usando precedência lexical). Por exemplo, considere este código: int 3,a; 3=5; a=3; Na instrução a = 3, 3 é interpretado como um identificador ou como um número? Isso causa ambiguidade. Espero que esteja claro.
Aml90 5/02

2
Eu também acho esse argumento fraco. Seria trivial escrever um lexer que aceitaria identificadores que começam com, mas não são inteiramente compostos por números.
Larry Gritz

11

Na maioria das vezes, isso não tem nada a ver com facilitar para os escritores do compilador e analisar a eficiência, mas mais com o design de uma sintaxe que incentive códigos legíveis e inequívocos.

Os designers da linguagem pensaram que seria bom poder escrever literais numéricos como o número 1 como apenas 1 .

Seria perfeitamente possível projetar uma sintaxe de linguagem onde literais numéricos fossem citados de alguma forma, por exemplo, tildas, de modo que o literal numérico do número um fosse codificado como ~ 1 ~ e qualquer coisa que não fosse uma palavra-chave e não estivesse entre aspas seria tratada como um nome de variável .

Então você pode codificar instruções como:

1 = ~2~
two = 1 * ~2~

Mas também:

2 = ~3~
six = 2 + 2

Seja qual for a sintaxe escolhida, o código ambíguo e difícil de seguir é inevitável.

A linguagem C e a maioria das linguagens de "colchetes" descendentes de C também consideraram uma boa idéia permitir que os programadores codificassem diretamente os literais Octal e Hexadecimal e especificar o tipo de literal, se isso fosse importante. tão

010  // Octal 10 = 8;
0x10 // Hexadecimal 10 = 16;
5l   // long integer with decimal value 5
2.0d // double float with value 2

Portanto, mesmo que você permita que nomes de variáveis ​​iniciem com um número seguido por uma combinação de números e letras que incluam pelo menos uma letra, você apresentaria ao programador o problema de decidir se um determinado grupo formou um nome de variável ou um literal numérico, de modo que

2lll = 22 // OK
2ll  = 2  // compiler error

Essa ambiguidade não ajudaria ninguém a escrever ou ler um programa.

Para um exemplo do mundo real estreitamente relacionado, você pode olhar para a linguagem PL / 1, cujos designers pensaram que poder usar palavras-chave como nomes de variáveis ​​era uma boa idéia para que:

IF THEN THEN THEN = ELSE; ELSE ELSE = THEN;
IF IF THEN ELSE = IF; ELSE THEN = ELSE;
DO WHILE (WHILE = DO); END = WHILE + DO; END;

É um código válido que compila e executa.


C foi projetado como montagem portátil para Unix. O Unix foi originalmente projetado para uma máquina de 18 bits, onde octal é um bom ajuste para impressão da mesma forma que hex é um bom ajuste para imprimir valores de máquinas de 8/16/32 bits. Por isso, eles realmente precisavam de octal.

Também para rodar bit (OR, XOR e AND, NOT) e implementar drivers de dispositivo, é importante especificar o tamanho exato de um literal e também o valor!
James Anderson

10

O Fortran teve um enorme efeito sobre como os idiomas posteriores foram projetados. Desde o início (alguns desses problemas foram corrigidos), o Fortran quase não tinha regras que restringissem o nome que você poderia dar a um identificador. Isso tornou a linguagem extremamente difícil de analisar tanto para compiladores quanto para programadores. Aqui está um exemplo clássico:

if if .eq. then then = else else else = endif endif
K  I   K   K    I      I    K    I      I     K

Aqui marquei as "palavras-chave do idioma" com K e os identificadores (nomes de variáveis) I. Dado que não há diferença na ortografia, acho que você provavelmente pode entender como isso pode ser confuso. Obviamente, este é um exemplo extremo e é improvável que alguém tenha escrito um código como esse de propósito. Às vezes as pessoas fizeram "reciclar" palavras-chave linguagem como nomes de identificadores embora - e em muitos casos, um erro de digitação simples podem resultar em código que a especificação linguagem disse deve ser analisado desta forma, mesmo que não se destinava a todos. Para outro exemplo conhecido, compare isso:

do 10 i = 1,10

para isso:

do 10 i = 1.10

O primeiro é um loop do - iterando um bloco de código 10 vezes. O segundo, no entanto, teve a vírgula alterada para um ponto decimal; portanto, está atribuindo o valor 1.10a uma variável chamada do 10 i.

Isso também significava que escrever um analisador Fortran era relativamente difícil - você não podia ter certeza de que o doinício da linha era realmente uma palavra-chave até chegar ao final da linha e verificar se todos os outros elementos de um doloop estavam presentes. O analisador geralmente tinha que estar pronto para "voltar atrás", analisando novamente a linha desde o início para chegar à resposta "correta" (mas muitas vezes não intencional) do que realmente estava lá.

Depois de alguns anos disso, os designers de idiomas (a maioria deles de qualquer maneira) foram para o extremo oposto - restringindo quase tudo sobre o idioma o máximo possível, sem que os usuários se queixassem demais .

No início, o BASIC, por exemplo, basicamente dizia que você não podia usar uma palavra-chave como parte de um identificador - por exemplo, fora=1seria analisado como for a = 1(isto é, o início de um forloop, não uma atribuição). Aparentemente, isso gerou reclamações suficientes e não durou muito. A regra sobre iniciar um identificador com um dígito aparentemente não gerou muitas reclamações, portanto continua sendo usada (pelo menos na maioria dos idiomas).


IMHO isso é o mais próximo do verdadeiro motivo. Idiomas antigos, como o Fortran, eram, de certa forma, muito desestruturados, levando a dificuldades na criação de compiladores robustos e a dificuldade de os humanos analisarem corretamente o código-fonte. O "do10i = ..." é um exemplo clássico e famoso. À medida que as línguas evoluíram, algumas das regras foram reforçadas. Algol é provavelmente o avô da regra padrão "identificadores começam com letras e, posteriormente, podem ter letras ou números".
Larry Gritz

FYI, o intérprete do Microsoft BASIC que formou a base das versões mais populares de microcomputadores do BASIC (incluindo Applesoft Basic e Commodore Basic) usou um tokenizer ganancioso para converter qualquer sequência de caracteres que correspondesse a um token de idioma em um valor de byte com o conjunto de bits altos. Isso foi feito sem nenhuma análise sintática. Então, ao executar o programa, o intérprete assumiria que todas as letras encontradas fossem parte de um nome de variável.
22612

1

É provável que essa convenção tenha evoluído de decisões muito precoces de design de linguagem histórica, pois nas primeiras máquinas o compilador inteiro, incluindo análise lexical, precisou rodar em alguns kWords, menos memória do que apenas o cache de dados do processador de primeiro nível nos dispositivos móveis atuais, portanto, os nomes de variáveis ​​permitidos eram muito limitados e precisavam ser fáceis de distinguir das constantes numéricas em muito poucos códigos op.

Assim, a convenção se tornou a que gerações de programadores estão acostumados.


1

Não é uma regra necessária para a linguagem de programação, mas apenas a convenção usada por muitos designers de linguagem.

Posso projetar uma linguagem radicalmente diferente que permita todos os caracteres para identificadores. Para todas as linhas de código, o primeiro caractere de 20 caracteres descreverá o tipo de instrução, o próximo caractere de 20 definirá o primeiro símbolo da instrução e o próximo caractere de 20 caracteres será operando para a instrução. Este idioma será executado em um processador de pilha.

01234567890123456789 01234567890123456789 01234567890123456789

decl symbol          12345                
assign value         12345                12345
decl symbol          99999                
assign value         99999                12345
push                 12345
push                 99999
add
print top

Este código pode ser traduzido em C, como abaixo:

int i12345 = 12345;
int i99999 = 12345;
printf("%d", i12345+i9999);

Isso é tudo. Não faz sentido, e a regra do número sem identificadores também não faz sentido em termos lógicos.


0

Além de "conveniência para o lexer", acho que também vale a pena considerar "conveniência para o leitor".

Ao ler o código, você precisa identificar rápida e repetidamente quais palavras são identificadores e quais são números. Procurar um dígito no início é mais fácil em nossa correspondência visual de padrões; seria uma tarefa se tivéssemos que verificar cuidadosamente todos os personagens para ter certeza.


0

A resposta a esta pergunta está no autômato ou no autômato finito, que define a expressão regular. A regra é ... os compiladores precisam de algoritmos ou regras exatas para decidir a cada caractere que analisam. Se fosse permitido aos identificadores começar com um número, o compilador estará em uma correção .. sobre a natureza do token que vem ... será um número ou um identificador ... e como os compiladores não podem voltar às posições anteriores .. .para deixar claro para o compilador que o token que vem é precisamente um identificador ou um número ... essa restrição existe ... porque isso ... o compilador sabe apenas verificando o primeiro caractere que o token que vem é um identificador ou um número.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.