Por que o tipo segue o nome da variável nas linguagens de programação modernas?

57

Por que quase todos os tipos de linguagens de programação modernas (Go, Rust, Kotlin, Swift, Scala, Nim e até Python) sempre vêm após o nome da variável na declaração da variável, e não antes?

Por que x: int = 42e não int x = 42?
O último não é mais legível que o primeiro?
É apenas uma tendência ou existem razões realmente significativas por trás dessa solução?

Andre Polykanine
fonte
10
Possível duplicata de Por que o Kotlin requer tipo após variável, e não antes?
precisa saber é o seguinte
3
Sim, isso é uma bobagem, tudo bem. Embora se refira especificamente ao idioma Kotlin, a pergunta (e suas respostas) se aplica a todos os idiomas que têm essa prática.
21716 Robert Harvey
2
Desculpe pessoal, eu vi a pergunta sobre o Kotlin quando pesquisava no Google, mas pedi novamente para não obter respostas como "Porque a equipe de desenvolvimento do Kotlin (Go, Rust, ...) decidiu assim". Eu estava interessado em uma resposta mais comum: por que isso se torna uma espécie de epidemia?
Andre Polykanine
2
Hihi, então Delphi, que é (Object) Pascal, e tem tipos após nomes de variáveis ​​desde o seu início, desde tempos remotos na idade das trevas do século anterior, é moderno novamente;) Btw, a legibilidade é muito afetada pelo que você é costumava. Vindo de Delphi, C # com seu tipo antes do nome var, me fez bater a cabeça por algum tempo.
Marjan Venema

Respostas:

64

Todos os idiomas que você mencionou suportam inferência de tipo , o que significa que o tipo é uma parte opcional da declaração nesses idiomas, porque são inteligentes o suficiente para preenchê-los quando você fornece uma expressão de inicialização que tem um tipo facilmente determinado.

Isso importa porque colocar as partes opcionais de uma expressão mais à direita reduz a ambiguidade da análise e aumenta a consistência entre as expressões que usam essa parte e as que não o fazem. É mais simples analisar uma declaração quando você sabe que a varpalavra - chave e o nome da variável são obrigatórios antes de acessar o material opcional. Em teoria, todas as coisas que facilitam a análise de computadores também devem melhorar a legibilidade geral para os seres humanos, mas isso é muito mais discutível.

Este argumento fica especialmente forte quando se consideram todos os modificadores opcionais tipo que uma língua "não-moderno", como C ++ tem, como *para ponteiros, &para referências, const, volatilee assim por diante. Depois de inserir vírgulas para várias declarações, você começa a obter algumas ambiguidades realmente estranhas, como int* a, b;não fazer bum ponteiro.

Até o C ++ agora suporta declarações "digite à direita" na forma de auto x = int{ 4 };, e possui algumas vantagens .

Ixrec
fonte
2
+1 por reconhecer a existência de palavras-chave obrigatórias como varessa fornece a maior parte da simplificação de análise.
precisa saber é o seguinte
2
A existência da varpalavra - chave não anula o objetivo da notação nome-primeiro? Eu não vejo a vantagem de escrever var userInput: String = getUserInput()sobre String userInput = getUserInput(). A vantagem seria relevante apenas se userInput = getUserInput()também for permitida, mas isso implicaria a declaração implícita de uma variável com escopo definido, o que eu acho bastante repugnante.
Kdb
2
@kdb em linguagens como C # e C ++ seria var userInput = getUserInput()ou auto userInput = getUserInput(), o compilador sabe getUserInput()retornos Stringpara que você não precise especificá-lo com a palavra-chave dedução de tipo. userInput = getUserInput()é claro que usar antes de declarar.
Wes Toleman 24/04
32

Por que em quase todas as linguagens de programação modernas (Go, Rust, Kotlin, Swift, Scala, Nim e até a última versão do Python) os tipos sempre vêm após a declaração da variável, e não antes?

Sua premissa é falha em duas frentes:

  • Existem novas linguagens de programação que têm o tipo antes do identificador. C♯, D ou Ceilão, por exemplo.
  • Tendo o tipo após o identificador não é um fenômeno novo, ele remonta a pelo menos Pascal (projetado 1968-1969, lançado em 1970), mas na verdade foi usado na teoria matemática dos tipos, que começa em 1902. Também foi usado em ML (1973), CLU (1974), Hope (1970), Modula-2 (1977-1985), Ada (1980), Miranda (1985), Caml (1985), Eiffel (1985), Oberon (1986), Modula-3 (1986-1988) e Haskell (1989).

Pascal, ML, CLU, Modula-2 e Miranda eram linguagens muito influentes; portanto, não é de surpreender que esse estilo de declaração de tipo permanecesse popular.

Por que x: int = 42e não int x = 42? O último não é mais legível que o primeiro?

A legibilidade é uma questão de familiaridade. Pessoalmente, acho o chinês ilegível, mas aparentemente o povo chinês não. Tendo aprendido Pascal na escola, brincado com Eiffel, F♯, Haskell e Scala, e tendo olhado para TypeScript, Fortress, Go, Rust, Kotlin, Idris, Frege, Agda, ML, Ocaml,…, parece completamente natural para mim .

É apenas uma tendência ou existem razões realmente significativas por trás dessa solução?

Se é uma tendência, é bastante persistente: como mencionei, remonta a cem anos em matemática.

Uma das principais vantagens de ter o tipo após o identificador é que é fácil deixar o tipo de fora, se você desejar que ele seja deduzido. Se suas declarações forem assim:

val i: Int = 10

Então é trivial deixar de fora o tipo e inferi-lo assim:

val i = 10

Considerando que, se o tipo vier antes do identificador, desta forma:

Int i = 10

Então, fica difícil para o analisador distinguir uma expressão de uma declaração:

i = 10

A solução que os designers de linguagem geralmente apresentam é a introdução de uma palavra-chave "Não quero escrever um tipo" que deve ser escrita em vez do tipo:

var  i = 10; // C♯
auto i = 10; // C++

Mas isso realmente não faz muito sentido: você basicamente precisa escrever explicitamente um tipo que diz que você não escreve um tipo. Hã? Seria muito mais fácil e sensato deixar de fora, mas isso torna a gramática muito mais complexa.

(E não vamos nem falar sobre os tipos de ponteiros de função em C.)

Os projetistas de várias das línguas mencionadas anteriormente abordaram esse assunto:

  • Go FAQ (consulte também: Sintaxe da declaração de Go ):

    Por que as declarações são invertidas?

    Eles estão apenas ao contrário se você estiver acostumado a C. Em C, a noção é que uma variável é declarada como uma expressão que indica seu tipo, o que é uma boa ideia, mas as gramáticas de tipo e expressão não se misturam muito bem e os resultados podem ser confusos; considere ponteiros de função. Go principalmente separa a expressão e o tipo de sintaxe e isso simplifica as coisas (o uso do prefixo *para ponteiros é uma exceção que comprova a regra). Em C, a declaração

    int* a, b;
    

    declara aser um ponteiro, mas não b; em Go

    var a, b *int
    

    declara que ambos são ponteiros. Isso é mais claro e mais regular. Além disso, o :=formulário de declaração abreviada argumenta que uma declaração variável completa deve apresentar a mesma ordem que :=a

    var a uint64 = 1
    

    tem o mesmo efeito que

    a := uint64(1)
    

    A análise também é simplificada por ter uma gramática distinta para tipos que não é apenas a gramática da expressão; palavras-chave como funce chanmantenha as coisas claras.

  • Perguntas frequentes sobre o Kotlin :

    Por que ter declarações de tipo à direita?

    Acreditamos que torna o código mais legível. Além disso, ele permite alguns recursos sintáticos agradáveis. Por exemplo, é fácil deixar anotações de tipo. Scala também provou muito bem que isso não é um problema.

  • Programação em Scala :

    O principal desvio do Java diz respeito à sintaxe das anotações de tipo - é " variable: Type" em vez de " Type variable" em Java. A sintaxe do tipo postfix de Scala se assemelha a Pascal, Modula-2 ou Eiffel. O principal motivo desse desvio está relacionado à inferência de tipo, que geralmente permite omitir o tipo de uma variável ou o tipo de retorno de um método. Usar a variable: Typesintaxe " " é fácil - deixe de fora os dois pontos e o tipo. Mas na Type variablesintaxe " " do estilo C, você não pode simplesmente deixar de fora o tipo - não haveria mais marcador para iniciar a definição. Você precisaria de alguma palavra-chave alternativa para ser um espaço reservado para um tipo ausente (o C♯ 3.0, que faz alguma inferência de tipo, usa varpara esse fim). Essa palavra-chave alternativa parece mais ad-hoc e menos regular do que a abordagem da Scala.

Nota: os designers do Ceilão também documentaram por que usam a sintaxe do tipo de prefixo :

Prefixo em vez de anotações do tipo postfix

Por que você segue C e Java colocando as anotações de tipo primeiro, em vez de Pascal e ML colocando-as após o nome da declaração?

Porque pensamos isso:

shared Float e = ....
shared Float log(Float b, Float x) { ... }

É simplesmente muito mais fácil de ler do que isso:

shared value e: Float = .... 
shared function log(b: Float, x: Float): Float { ... }

E simplesmente não entendemos como alguém poderia pensar o contrário!

Pessoalmente, acho o "argumento" deles muito menos convincente do que os outros.

Jörg W Mittag
fonte
4
Parece que a capacidade de deixar de fora o tipo e ainda temos simples análise é concedido pela adição de var, auto, letou semelhantes, não por que lado da variável a declaração de tipo está ligado. var Int x = 5ou até Int var x = 5poderiam ser reduzidos para var x = 5.
precisa saber é o seguinte
5
Embora eu goste da leitura igualmente fácil quando você se acostumar e o argumento do tipo opcional for forte, gostaria de salientar que o IDE não é capaz de propor automaticamente nomes de variáveis ​​com base no tipo. Sinto falta disso quando escrevo TypeScript.
23618 Pierre Pierre
"Your premise is flawed on two fronts" Todos esses são mais recentes que C # ou D.
Det
3
"Mas isso realmente não faz muito sentido: você basicamente tem que escrever explicitamente um tipo que diz que você não escreve um tipo". Bem, a versão digitar à direita é exatamente a mesma no seu exemplo ... Também discordo que não faz sentido. Faz exatamente tanto sentido quanto funcem Go.
Sopel
4

Pascal faz isso também por sinal, e não é um novo idioma. Era uma linguagem acadêmica, projetada do zero.

Eu diria que é semanticamente mais claro começar com o nome da variável. O tipo é apenas um detalhe técnico. Se você quiser ler sua classe como o modelo de realidade atual, faz sentido colocar os nomes de suas entidades em primeiro lugar e sua implementação técnica por último.

C # e java derivam de C, então eles tiveram que respeitar a "arte anterior", para não confundir o programador experiente.

Martin Maat
fonte
3
Ada também faz dessa maneira, mas certamente não é uma "linguagem acadêmica".
1911 John R. Strohm
Ada fez isso porque ele plagiou fortemente a partir de Pascal e Modula 2.
Gort the Robot
2
@StevenBurnap, uma pesquisa rápida revela que o primeiro livro de Wirth sobre o Modula-2 foi lançado em meados de 1982. Ada estava em desenvolvimento vários anos antes disso (o DoD1 estava em 1977, pelo que me lembro), e padronizou, como um padrão ANSI e um MIL-STD, em 1983. Todas as quatro equipes que apresentaram candidatos ao Ada fizeram o PASCAL como pontos de partida. (Laboratórios Bell foi convidado pelo DoD para enviar uma linguagem candidato baseado em C. Eles objetou, dizendo que C não foi seguida, e não iria nunca ser robusto o suficiente para o software de missão crítica DoD.)
John R. Strohm
Eu devo estar me lembrando. Eu pensei que Wirth estivesse envolvido em Ada.
Gort the Robot
Pascal começou uma linguagem acadêmica, mas até o final da década de 90 ele estava à beira de ser a linguagem para escrever aplicativos do Windows em via Delphi, que é até Borland pulou o tubarão com preços e deixou a porta aberta para Java e C # para venha e roube o prêmio. Você ainda verá uma tonelada de Delphi no mundo dos aplicativos Windows herdados.
Shayne
1

Por que x: int = 42e não int x = 42? O último não é mais legível que o primeiro?

Em um exemplo tão simples, não há muita diferença, mas vamos torná-lo um pouco mais complexo: int* a, b;

Esta é uma declaração válida e real em C, mas não faz o que parece intuitivamente que deveria fazer. Parece que estamos declarando duas variáveis ​​do tipo int*, mas na verdade estamos declarando uma int*e uma int.

Se o seu idioma colocar o tipo após o (s) nome (s) da variável em suas declarações, é impossível encontrar esse problema.

Mason Wheeler
fonte
4
Não sei por que mover a declaração de tipo depois afetaria isso. São x, y *int;dois *intou um *inte um int? Fazendo int*ou *intum símbolo em vez de dois iria resolvê-lo, ou usando um elemento sintática adicional, como o :que poderia trabalhar em qualquer direção.
precisa saber é o seguinte
@ 8bittree Todos os idiomas que eu conheço e que usam declarações de primeiro nome usam um token adicional, como :ou as, entre o nome e o tipo.
Mason Wheeler
2
E em Go, x, y *intsignifica "declarar duas variáveis, x e y, com o tipo ponteiro para int".
Vatine
10
O C # usa a sintaxe do prefixo, mas trata int[] x, ycomo duas matrizes. É apenas a tola sintaxe C que trata esses modificadores como operadores unários na variável (e uma mistura de prefixo e postfix, nada menos) que causa problemas.
CodesInChaos