Leo el código con mucha más frecuencia que escribo código, y supongo que la mayoría de los programadores que trabajan en software industrial hacen esto. La ventaja de la inferencia de tipos, supongo, es menos verbosidad y menos código escrito. Pero, por otro lado, si lee el código con más frecuencia, probablemente querrá un código legible.
El compilador infiere el tipo; Hay viejos algoritmos para esto. Pero la verdadera pregunta es ¿por qué yo, el programador, querría inferir el tipo de mis variables cuando leo el código? ¿No es más rápido para alguien leer el tipo que pensar qué tipo hay?
Editar: Como conclusión, entiendo por qué es útil. Pero en la categoría de características del lenguaje, lo veo en un cubo con sobrecarga del operador, útil en algunos casos pero que afecta la legibilidad si se abusa.
fuente
Respuestas:
Echemos un vistazo a Java. Java no puede tener variables con tipos inferidos. Esto significa que con frecuencia tengo que deletrear el tipo, incluso si es perfectamente obvio para un lector humano cuál es el tipo:
Y a veces es simplemente molesto explicar todo el tipo.
Este tipeo estático detallado se interpone en mi camino, el programador. La mayoría de las anotaciones de tipo son rellenos repetitivos de línea, regurgitaciones sin contenido de lo que ya sabemos. Sin embargo, me gusta la escritura estática, ya que realmente puede ayudar a descubrir errores, por lo que usar la escritura dinámica no siempre es una buena respuesta. La inferencia de tipos es lo mejor de ambos mundos: puedo omitir los tipos irrelevantes, pero aún así estar seguro de que mi programa (type-) se desprotege.
Si bien la inferencia de tipos es realmente útil para las variables locales, no debe usarse para las API públicas que deben documentarse sin ambigüedades. Y a veces los tipos son realmente críticos para comprender lo que está sucediendo en el código. En tales casos, sería una tontería confiar solo en la inferencia de tipos.
Hay muchos idiomas que admiten la inferencia de tipos. Por ejemplo:
C ++. La
auto
palabra clave activa la inferencia de tipos. Sin él, deletrear los tipos para lambdas o para entradas en contenedores sería un infierno.DO#. Puede declarar variables con
var
, lo que desencadena una forma limitada de inferencia de tipos. Todavía administra la mayoría de los casos en los que desea inferencia de tipos. En ciertos lugares, puede omitir el tipo por completo (por ejemplo, en lambdas).Haskell y cualquier idioma de la familia ML. Si bien el sabor específico de la inferencia de tipos que se usa aquí es bastante poderoso, a menudo se ven anotaciones de tipo para funciones, y por dos razones: la primera es documentación, y la segunda es una verificación de que la inferencia de tipo realmente encontró los tipos que esperaba. Si hay una discrepancia, es probable que haya algún tipo de error.
fuente
int
, podría ser cualquier tipo numérico, incluso parchar
. Además, no entiendo por qué querrías deletrear todo el tipo paraEntry
cuando solo puedes escribir el nombre de la clase y dejar que tu IDE haga la importación necesaria. El único caso cuando tiene que deletrear el nombre completo es cuando tiene una clase con el mismo nombre en su propio paquete. Pero me parece un mal diseño de todos modos.int
ejemplo, estaba pensando en (en mi opinión, un comportamiento bastante sensato) de la mayoría de los idiomas que presentan inferencia de tipos. Por lo general, infieren unint
oInteger
como se llame en ese idioma. La belleza de la inferencia de tipos es que siempre es opcional; aún puede especificar un tipo diferente si lo necesita. En cuanto alEntry
ejemplo: buen punto, lo reemplazaré conMap.Entry<Integer, Map<Integer, SomeObject<SomeObject, T>>>
. Java ni siquiera tiene alias de tipo :(colKey
es obvio e irrelevante: solo nos importa que sea adecuado como segundo argumento dedoSomethingWith
. Si(key1, key2, value)
tuviera que extraer ese bucle en una función que produce un Iterable de -triples, la firma más general sería<K1, K2, V> Iterable<TableEntry<K1, K2, V>> flattenTable(Map<K1, Map<K2, V>> table)
. Dentro de esa función, el tipo real decolKey
(Integer
, noK2
) es absolutamente irrelevante.View.OnClickListener listener = new View.OnClickListener()
. Todavía sabría el tipo incluso si el programador era "vago" y lo acortabavar listener = new View.OnClickListener
(si esto fuera posible). Este tipo de redundancia es común - No voy a arriesgar un cálculo aproximado aquí - y retirarlo no se derivan de pensar en futuros lectores. Cada característica del lenguaje debe usarse con cuidado, no estoy cuestionando eso.Es cierto que el código se lee con mucha más frecuencia de lo que se escribe. Sin embargo, la lectura también lleva tiempo, y dos pantallas de código son más difíciles de navegar y leer que una pantalla de código, por lo que debemos priorizar para empacar la mejor relación de información útil / esfuerzo de lectura. Este es un principio general de UX: demasiada información a la vez abruma y en realidad degrada la efectividad de la interfaz.
Y es mi experiencia que a menudo , el tipo exacto no es (tan) importante. Seguramente usted a veces anidar expresiones:
x + y * z
,monkey.eat(bananas.get(i))
,factory.makeCar().drive()
. Cada uno de estos contiene subexpresiones que evalúan un valor cuyo tipo no está escrito. Sin embargo, son perfectamente claros. Estamos de acuerdo en dejar el tipo sin declarar porque es bastante fácil de entender del contexto, y escribirlo haría más daño que bien (desordenar la comprensión del flujo de datos, ocupar una valiosa pantalla y espacio de memoria a corto plazo).Una razón para no anidar expresiones como si no hubiera un mañana es que las líneas se alargan y el flujo de valores se vuelve poco claro. La introducción de una variable temporal ayuda con esto, impone un orden y le da un nombre a un resultado parcial. Sin embargo, no todo lo que se beneficia de estos aspectos también se beneficia de tener su tipo enunciado:
¿Importa si
user
es un objeto de entidad, un entero, una cadena u otra cosa? Para la mayoría de los propósitos, no es suficiente, es suficiente saber que representa a un usuario, proviene de la solicitud HTTP y se utiliza para buscar el nombre que se mostrará en la esquina inferior derecha de la respuesta.Y cuando se hace materia, el autor es libre de escribir el tipo. Esta es una libertad que debe usarse de manera responsable, pero lo mismo es cierto para todo lo demás que puede mejorar la legibilidad (nombres de variables y funciones, formato, diseño de API, espacio en blanco). Y, de hecho, la convención en Haskell y ML (donde todo se puede inferir sin esfuerzo adicional) es escribir los tipos de funciones no locales, y también de las variables y funciones locales cuando sea apropiado. Solo los novatos permiten inferir cada tipo.
fuente
user
importa si está intentando extender la función, porque determina lo que puede hacer con eluser
. Esto es importante si desea agregar algún control de cordura (debido a una vulnerabilidad de seguridad, por ejemplo), u olvidó que realmente necesita hacer algo con el usuario además de solo mostrarlo. Es cierto que estos tipos de lectura para expansión son menos frecuentes que solo leer el código, pero también son una parte vital de nuestro trabajo.Creo que la inferencia de tipos es bastante importante y debería admitirse en cualquier idioma moderno. Todos nos desarrollamos en IDE y podrían ayudarnos mucho en caso de que desee conocer el tipo inferido, solo algunos de nosotros pirateamos
vi
. Piense en el código de verbosidad y ceremonia en Java, por ejemplo.Pero puedes decir que está bien, mi IDE me ayudará, podría ser un punto válido. Sin embargo, algunas características no estarían allí sin la ayuda de la inferencia de tipos, por ejemplo, tipos anónimos de C #.
Linq no sería tan bueno como lo es ahora sin la ayuda de la inferencia de tipos,
Select
por ejemploEste tipo anónimo se deducirá claramente a la variable.
No me gusta la inferencia de tipos en los tipos de retorno
Scala
porque creo que su punto se aplica aquí, debería estar claro para nosotros qué devuelve una función para que podamos usar la API con más fluidezfuente
Map<String,HashMap<String,String>>
? Claro, si no está usando tipos, deletrearlos tiene poco beneficio.Table<User, File, String>
es más informativo y hay un beneficio en escribirlo.Creo que la respuesta a esto es realmente simple: ahorra leer y escribir información redundante. Particularmente en lenguajes orientados a objetos donde tiene un tipo en ambos lados del signo igual.
Lo que también le dice cuándo debe o no usarlo, cuando la información no es redundante.
fuente
Supongamos que uno ve el código:
Si
someBigLongGenericType
es asignable por el tipo de retorno desomeFactoryMethod
, ¿qué tan probable sería que alguien que lea el código se dé cuenta si los tipos no coinciden con precisión, y con qué facilidad podría alguien que notó la discrepancia reconocer si fue intencional o no?Al permitir la inferencia, un lenguaje puede sugerir a alguien que está leyendo el código que cuando se establece explícitamente el tipo de una variable, la persona debe tratar de encontrar una razón para ello. Esto a su vez permite a las personas que leen código enfocar mejor sus esfuerzos. Si, por el contrario, la gran mayoría de las veces cuando se especifica un tipo, resulta ser exactamente el mismo que se habría inferido, entonces alguien que está leyendo el código puede ser menos propenso a notar las veces que es sutilmente diferente .
fuente
Veo que ya hay una serie de buenas respuestas. Algunas de las cuales repetiré, pero a veces solo quieres poner las cosas en tus propias palabras. Comentaré con algunos ejemplos de C ++ porque ese es el lenguaje con el que estoy más familiarizado.
Lo que es necesario nunca es imprudente. La inferencia de tipos es necesaria para hacer prácticas otras características del lenguaje. En C ++ es posible tener tipos indescriptibles.
C ++ 11 agregó lambdas que también son indescriptibles.
La inferencia de tipos también sustenta las plantillas.
Pero sus preguntas fueron "¿por qué yo, el programador, querría inferir el tipo de mis variables cuando leo el código? ¿No es más rápido para alguien leer el tipo que pensar qué tipo hay?"
La inferencia de tipos elimina la redundancia. Cuando se trata de leer el código, a veces puede ser más rápido y fácil tener información redundante en el código, pero la redundancia puede eclipsar la información útil . Por ejemplo:
No hace falta mucha familiaridad con la biblioteca estándar para que un programador de C ++ identifique que soy un iterador, por
i = v.begin()
lo que la declaración de tipo explícita es de valor limitado. Por su presencia, oculta detalles que son más importantes (como los quei
apuntan al comienzo del vector). La buena respuesta de @amon proporciona un ejemplo aún mejor de verbosidad que eclipsa detalles importantes. En contraste, el uso de la inferencia de tipos le da mayor importancia a los detalles importantes.Si bien la lectura del código es importante, no es suficiente, en algún momento tendrá que dejar de leer y comenzar a escribir código nuevo. La redundancia en el código hace que la modificación del código sea más lenta y difícil. Por ejemplo, supongamos que tengo el siguiente fragmento de código:
En el caso de que necesite cambiar el tipo de valor del vector para cambiar el código dos veces a:
En este caso, tengo que modificar el código en dos lugares. Contraste con la inferencia de tipos donde el código original es:
Y el código modificado:
Tenga en cuenta que ahora solo tengo que cambiar una línea de código. Extrapolar esto a un programa grande y la inferencia de tipos puede propagar los cambios a los tipos mucho más rápido que con un editor.
La redundancia en el código crea la posibilidad de errores. Cada vez que su código depende de que dos datos se mantengan equivalentes, existe la posibilidad de error. Por ejemplo, hay una inconsistencia entre los dos tipos en esta declaración que probablemente no se pretende:
La redundancia hace que la intención sea más difícil de discernir. En algunos casos, la inferencia de tipos puede ser más fácil de leer y comprender porque es más simple que la especificación de tipo explícita. Considere el fragmento de código:
En el caso de que
sq(x)
devuelva unint
, no es obvio siy
es unint
porque es el tipo de retorno desq(x)
o porque se ajusta a las declaraciones que utilizany
. Si cambio otro código de modo quesq(x)
ya no regreseint
, es incierto de esa línea solo si el tipo dey
debe actualizarse. Contraste con el mismo código pero usando inferencia de tipos:En esto, la intención es clara,
y
debe ser del mismo tipo que devuelto porsq(x)
. Cuando el código cambia el tipo de retorno desq(x)
, el tipo dey
cambios para que coincida automáticamente.En C ++ hay una segunda razón por la cual el ejemplo anterior es más simple con la inferencia de tipos, la inferencia de tipos no puede introducir la conversión de tipos implícita. Si el tipo de retorno de
sq(x)
no esint
, el compilador con silenciosamente inserta una conversión implícita aint
. Si el tipo de retorno desq(x)
es un tipo complejo de tipo que defineoperator int()
, esta llamada de función oculta puede ser arbitrariamente compleja.fuente
typeof
se vuelve inútil por el lenguaje. Y ese es un déficit del lenguaje en sí mismo que debería solucionarse en mi opinión.