¿Por qué el tipo va después del nombre de la variable en los lenguajes de programación modernos?

57

¿Por qué en casi todos los lenguajes de programación modernos (Go, Rust, Kotlin, Swift, Scala, Nim, incluso la última versión de Python) los tipos siempre vienen después del nombre de la variable en la declaración de la variable, y no antes?

¿Por qué x: int = 42y no int x = 42?
¿No es este último más legible que el primero?
¿Es solo una tendencia o hay alguna razón realmente significativa detrás de esta solución?

Andre Polykanine
fuente
3
Sí, eso es un engaño bien. Aunque se refiere específicamente al idioma Kotlin, la pregunta (y sus respuestas) se aplican a todos los idiomas que tienen esta práctica.
Robert Harvey
2
Lo siento chicos, vi la pregunta sobre Kotlin cuando busqué en Google, pero volví a pedirla para no obtener respuestas como "Porque el equipo de desarrollo de Kotlin (Go, Rust, ...) decidió así". Estaba interesado en una respuesta más común: ¿por qué se convierte en una especie de epidemia?
Andre Polykanine
2
Hola, entonces Delphi, que es (Objeto) Pascal, y ha tenido tipos después de nombres de variables desde su inicio, allá por la edad oscura del siglo anterior, es moderno de nuevo;) Por cierto, la legibilidad se ve muy afectada por lo que eres Acostumbrado a. Viniendo de Delphi, C # con su tipo antes del nombre var, me hizo golpear la cabeza durante bastante tiempo.
Marjan Venema

Respuestas:

64

Todos los idiomas que mencionó admiten la inferencia de tipos , lo que significa que el tipo es una parte opcional de la declaración en esos idiomas porque son lo suficientemente inteligentes como para completarlos cuando proporciona una expresión de inicialización que tiene un tipo fácil de determinar.

Eso es importante porque poner las partes opcionales de una expresión más a la derecha reduce la ambigüedad de análisis y aumenta la coherencia entre las expresiones que usan esa parte y las que no. Es más simple analizar una declaración cuando sabes que la varpalabra clave y el nombre de la variable son obligatorios antes de llegar a las cosas opcionales. En teoría, todas esas cosas que facilitan el análisis de las computadoras también deberían mejorar la legibilidad general para los humanos, pero eso es mucho más discutible.

Este argumento se pone especialmente fuerte cuando se tiene en cuenta todos los modificadores de tipo opcional que un lenguaje "no modernos" como C ++ tiene, como *para los punteros, &para las referencias, const, volatiley así sucesivamente. Una vez que agrega comas para varias declaraciones, comienza a obtener algunas ambigüedades realmente extrañas, como int* a, b;no hacer bun puntero.

Incluso C ++ ahora admite declaraciones de "tipo a la derecha" en forma de auto x = int{ 4 };, y tiene algunas ventajas .

Ixrec
fuente
2
+1 por reconocer la existencia de palabras clave obligatorias como varesa proporcionan la mayoría de la simplificación del análisis.
8bittree
2
¿La existencia de la varpalabra clave no vence el propósito de la notación de primer nombre? No veo la ventaja de escribir var userInput: String = getUserInput()sobre String userInput = getUserInput(). La ventaja solo sería relevante si userInput = getUserInput()también se permite, pero eso implicaría la declaración implícita de una variable de ámbito, lo que me parece bastante aborrecible.
kdb
2
@kdb en lenguajes como C # y C ++ sería var userInput = getUserInput()o auto userInput = getUserInput(), el compilador sabe getUserInput()retornos Stringpara que no tenga que especificarlo con la palabra clave de deducción de tipo. userInput = getUserInput()es, por supuesto, usar antes de declarar.
Wes Toleman
32

¿Por qué en casi todos los lenguajes de programación modernos (Go, Rust, Kotlin, Swift, Scala, Nim, incluso la última versión de Python) los tipos siempre vienen después de la declaración de variable, y no antes?

Su premisa es defectuosa en dos frentes:

  • Hay nuevos lenguajes de programación que tienen el tipo antes del identificador. C♯, D o Ceilán, por ejemplo.
  • Tener el tipo después del identificador no es un fenómeno nuevo, se remonta al menos a Pascal (diseñado entre 1968 y 1969, lanzado en 1970), pero en realidad se usó en la teoría matemática de tipos, que comienza en 1902. También se usó en ML (1973), CLU (1974), Hope (1970), Modula-2 (1977-1985), Ada (1980), Miranda (1985), Caml (1985), Eiffel (1985), Oberon (1986), Modula-3 (1986–1988) y Haskell (1989).

Pascal, ML, CLU, Modula-2 y Miranda fueron idiomas muy influyentes, por lo que no es sorprendente que este estilo de declaraciones tipográficas siguiera siendo popular.

¿Por qué x: int = 42y no int x = 42? ¿No es este último más legible que el primero?

La legibilidad es una cuestión de familiaridad. Personalmente, encuentro que los chinos son ilegibles, pero aparentemente, los chinos no lo hacen. Después de haber aprendido a Pascal en la escuela, incursionar en Eiffel, F Has, Haskell y Scala, y haber examinado TypeScript, Fortress, Go, Rust, Kotlin, Idris, Frege, Agda, ML, Ocaml, ... me parece completamente natural. .

¿Es solo una tendencia o hay alguna razón realmente significativa detrás de tal solución?

Si es una tendencia, es bastante persistente: como mencioné, se remonta a cien años en matemáticas.

Una de las principales ventajas de tener el tipo después del identificador, es que es fácil omitir el tipo si desea inferirlo. Si sus declaraciones se ven así:

val i: Int = 10

Entonces es trivial dejar de lado el tipo e inferirlo así:

val i = 10

Mientras que si el tipo viene antes del identificador como este:

Int i = 10

Entonces comienza a ser difícil para el analizador distinguir una expresión de una declaración:

i = 10

La solución que los diseñadores de idiomas suelen presentar es introducir una palabra clave "No quiero escribir un tipo" que debe escribirse en lugar del tipo:

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

Pero esto en realidad no tiene mucho sentido: básicamente tienes que escribir explícitamente un tipo que dice que no escribes un tipo. ¿Eh? Sería mucho más fácil y más sensato dejarlo fuera, pero eso hace que la gramática sea mucho más compleja.

(Y ni siquiera hablemos sobre los tipos de puntero de función en C.)

Los diseñadores de varios de los idiomas mencionados anteriormente han intervenido en este tema:

  • Preguntas frecuentes sobre Go (consulte también: Sintaxis de declaración de Go ):

    ¿Por qué las declaraciones son al revés?

    Solo están al revés si estás acostumbrado a C. En C, la noción es que una variable se declara como una expresión que denota su tipo, lo cual es una buena idea, pero las gramáticas de tipo y expresión no se mezclan muy bien y los resultados pueden ser confusos; considerar punteros de función. Ir separa principalmente la sintaxis de expresión y tipo y eso simplifica las cosas (el uso de prefijos *para punteros es una excepción que prueba la regla). En C, la declaración

    int* a, b;
    

    declara aser un puntero pero no b; en Go

    var a, b *int
    

    declara que ambos son punteros. Esto es más claro y más regular. Además, el :=formulario de declaración corta argumenta que una declaración de variable completo debe presentar el mismo orden que :=por lo

    var a uint64 = 1
    

    tiene el mismo efecto que

    a := uint64(1)
    

    El análisis también se simplifica al tener una gramática distinta para los tipos que no es solo la gramática de la expresión; palabras clave como funcy chanmantener las cosas claras.

  • Preguntas frecuentes sobre Kotlin :

    ¿Por qué tener declaraciones de tipo a la derecha?

    Creemos que hace que el código sea más legible. Además, permite algunas características sintácticas agradables. Por ejemplo, es fácil dejar de lado las anotaciones de tipo. Scala también ha demostrado bastante bien que esto no es un problema.

  • Programación en Scala :

    La principal desviación de Java se refiere a la sintaxis para las anotaciones de tipo: es " variable: Type" en lugar de " Type variable" en Java. La sintaxis de tipo postfix de Scala se parece a Pascal, Modula-2 o Eiffel. La razón principal de esta desviación tiene que ver con la inferencia de tipos, que a menudo le permite omitir el tipo de una variable o el tipo de retorno de un método. Usar la variable: Typesintaxis " " es fácil: simplemente omita los dos puntos y el tipo. Pero en la Type variablesintaxis " " de estilo C, no puede simplemente omitir el tipo: ya no habría ningún marcador para comenzar la definición. Necesitaría alguna palabra clave alternativa para ser un marcador de posición para un tipo faltante (C♯ 3.0, que hace alguna inferencia de tipo, utiliza varpara este propósito). Dicha palabra clave alternativa se siente más ad-hoc y menos regular que el enfoque de Scala.

Nota: los diseñadores de Ceilán también documentaron por qué usan la sintaxis de tipo de prefijo :

Anotaciones de tipo prefijo en lugar de postfijo

¿Por qué sigues C y Java al poner primero las anotaciones de tipo, en lugar de Pascal y ML al ponerlas después del nombre de la declaración?

Porque pensamos esto:

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

Es simplemente mucho más fácil de leer que esto:

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

¡Y simplemente no entendemos cómo alguien podría pensar lo contrario!

Personalmente, encuentro su "argumento" mucho menos convincente que los otros.

Jörg W Mittag
fuente
44
Parece que la capacidad de dejar de lado el tipo y todavía tienen análisis sencillo se concede por la adición de var, auto, let, o similares, y no por qué lado de la variable de la declaración de tipo está encendido. var Int x = 5o incluso Int var x = 5podría ser acortado a ambos var x = 5.
8bittree
55
Si bien lo considero igualmente legible una vez que te acostumbras y el argumento de tipo opcional es fuerte, me gustaría señalar que el IDE no puede proponer automáticamente nombres de variables basados ​​en el tipo. Echo de menos eso cuando escribo TypeScript.
Pierre Henry
"Your premise is flawed on two fronts" Todos estos son más nuevos que C # o D.
Det
3
"Pero esto en realidad no tiene mucho sentido: básicamente tienes que escribir explícitamente un tipo que dice que no escribes un tipo". Bueno, la versión de escribir a la derecha es exactamente la misma en su ejemplo ... Además, no estoy de acuerdo con que no tenga sentido. Tiene exactamente tanto sentido como funcen Go.
Sopel
4

Pascal también lo hace, y no es un idioma nuevo. Sin embargo, era un lenguaje académico, diseñado desde cero.

Diría que es semánticamente más claro comenzar con el nombre de la variable. El tipo es solo un detalle técnico. Si desea leer su clase como el modelo de realidad que es, tiene sentido poner los nombres de sus entidades primero y su implementación técnica al final.

C # y Java provienen de C, por lo que tuvieron que respetar la "técnica anterior", para no confundir al programador experimentado.

Martin Maat
fuente
3
Ada también lo hace de esa manera, pero ciertamente no es un "lenguaje académico".
John R. Strohm
Ada lo hizo porque pesaba mucho de Pascal y Modula 2.
Gort the Robot
2
@StevenBurnap, una búsqueda rápida revela que el primer libro de Wirth sobre Modula-2 salió a mediados de 1982. Ada estaba en desarrollo varios años antes (DoD1 fue en 1977 más o menos, según recuerdo) y se estandarizó, tanto como estándar ANSI como MIL-STD, en 1983. Los cuatro equipos que presentaron candidatos para Ada tomaron PASCAL como sus puntos de partida. (El Departamento de Defensa invitó a Bell Labs a enviar un lenguaje candidato basado en C. Se negaron, diciendo que C no era entonces y que nunca sería lo suficientemente robusto para el software de misión crítica del DoD).
John R. Strohm,
Debo estar recordando mal. Pensé que Wirth había estado involucrado en Ada.
Gort the Robot
Pascal comenzó un lenguaje académico, pero a fines de los 90 estaba a punto de ser el idioma para escribir aplicaciones de Windows a través de Delphi, hasta que Borland saltó al tiburón con los precios y dejó la puerta abierta para Java y C # para ven y roba el premio. Sin embargo, todavía verá una tonelada de Delphi en el mundo de las aplicaciones de Windows heredadas.
Shayne
1

¿Por qué x: int = 42y no int x = 42? ¿No es este último más legible que el primero?

En un ejemplo tan simple, no hay mucha diferencia, pero hagámoslo un poco más complejo: int* a, b;

Esta es una declaración real y válida en C, pero no hace lo que intuitivamente parece que debería hacer. Parece que estamos declarando dos variables de tipo int*, pero en realidad estamos declarando una int*y una int.

Si su idioma coloca el tipo después de los nombres de las variables en sus declaraciones, es imposible encontrar ese problema.

Mason Wheeler
fuente
44
No estoy seguro de por qué mover la declaración de tipo después afectaría esto. ¿Es x, y *int;dos *into uno *inty uno int? Hacer int*o *intuna ficha en lugar de dos lo resolvería, o usar un elemento sintáctico adicional como el :, que podría funcionar en cualquier dirección.
8bittree
@ 8bittree Todos los idiomas con los que estoy familiarizado que usan declaraciones de nombre primero usan un token adicional, como :o as, entre el nombre y el tipo.
Mason Wheeler
2
Y en Go, x, y *intsignifica "declarar dos variables, x e y, con el tipo puntero a int".
Vatine
10
C # usa la sintaxis del prefijo, pero trata int[] x, ycomo dos matrices. Es solo la sintaxis C tonta que trata esos modificadores como operadores unarios en la variable (y una mezcla de prefijo y postfix, nada menos) que causa problemas.
CodesInChaos