¿Cuáles son las ventajas y limitaciones de los lenguajes de tipo dinámico en comparación con los lenguajes de tipo estático?
Ver también : ¿qué pasa con el amor por los lenguajes dinámicos (un hilo mucho más argumentativo ...)
¿Cuáles son las ventajas y limitaciones de los lenguajes de tipo dinámico en comparación con los lenguajes de tipo estático?
Ver también : ¿qué pasa con el amor por los lenguajes dinámicos (un hilo mucho más argumentativo ...)
Respuestas:
La capacidad del intérprete para deducir el tipo y las conversiones de tipo hace que el tiempo de desarrollo sea más rápido, pero también puede provocar fallas en el tiempo de ejecución que simplemente no puede obtener en un lenguaje estáticamente tipado donde las atrapa en tiempo de compilación. Pero cuál es mejor (o incluso si eso es siempre cierto) se discute en la comunidad en estos días (y desde hace mucho tiempo).
Un buen punto de vista sobre el tema es la escritura estática donde sea posible, la escritura dinámica cuando sea necesario: el fin de la guerra fría entre lenguajes de programación por Erik Meijer y Peter Drayton en Microsoft:
Los defensores de la escritura estática argumentan que las ventajas de la escritura estática incluyen la detección temprana de errores de programación (por ejemplo, evitar agregar un número entero a un booleano), una mejor documentación en forma de firmas de tipo (por ejemplo, incorporar números y tipos de argumentos al resolver nombres), más Oportunidades para las optimizaciones del compilador (por ejemplo, reemplazar llamadas virtuales por llamadas directas cuando se conoce estáticamente el tipo exacto del receptor), mayor eficiencia de tiempo de ejecución (por ejemplo, no todos los valores deben llevar un tipo dinámico) y una mejor experiencia del desarrollador en tiempo de diseño (por ejemplo, conocer el tipo de receptor, el IDE puede presentar un menú desplegable de todos los miembros aplicables). Los fanáticos de la escritura estática intentan hacernos creer que "los programas bien escritos no pueden salir mal". Si bien esto ciertamente suena impresionante, Es una declaración bastante vacía. La verificación de tipo estático es una abstracción en tiempo de compilación del comportamiento en tiempo de ejecución de su programa y, por lo tanto, es necesariamente solo parcialmente e incompleto. Esto significa que los programas aún pueden salir mal debido a las propiedades que no son rastreadas por el verificador de tipo, y que hay programas que, aunque no pueden salir mal, no pueden verificarse. El impulso de hacer que la escritura estática sea menos parcial y más completa hace que los sistemas de tipos se vuelvan demasiado complicados y exóticos, como lo demuestran conceptos como "tipos fantasma" [11] y "tipos tambaleantes" [10]. Esto es como tratar de correr un maratón con una pelota y una cadena atadas a la pierna y gritar triunfante que casi lo logras a pesar de que saliste después de la primera milla. La verificación de tipo estático es una abstracción en tiempo de compilación del comportamiento en tiempo de ejecución de su programa y, por lo tanto, es necesariamente solo parcialmente e incompleto. Esto significa que los programas aún pueden salir mal debido a las propiedades que no son rastreadas por el verificador de tipo, y que hay programas que, aunque no pueden salir mal, no pueden verificarse. El impulso de hacer que la escritura estática sea menos parcial y más completa hace que los sistemas de tipos se vuelvan demasiado complicados y exóticos, como lo demuestran conceptos como "tipos fantasma" [11] y "tipos tambaleantes" [10]. Esto es como tratar de correr un maratón con una pelota y una cadena atadas a la pierna y gritar triunfante que casi lo logras a pesar de que saliste después de la primera milla. La verificación de tipo estático es una abstracción en tiempo de compilación del comportamiento en tiempo de ejecución de su programa y, por lo tanto, es necesariamente solo parcialmente e incompleto. Esto significa que los programas aún pueden salir mal debido a las propiedades que no son rastreadas por el verificador de tipo, y que hay programas que, aunque no pueden salir mal, no pueden verificarse. El impulso de hacer que la escritura estática sea menos parcial y más completa hace que los sistemas de tipos se vuelvan demasiado complicados y exóticos, como lo demuestran conceptos como "tipos fantasma" [11] y "tipos tambaleantes" [10]. Esto es como tratar de correr un maratón con una pelota y una cadena atadas a la pierna y gritar triunfante que casi lo logras a pesar de que saliste después de la primera milla. y por lo tanto, es necesariamente solo parcialmente e incompleto. Esto significa que los programas aún pueden salir mal debido a las propiedades que no son rastreadas por el verificador de tipo, y que hay programas que, aunque no pueden salir mal, no pueden verificarse. El impulso de hacer que la escritura estática sea menos parcial y más completa hace que los sistemas de tipos se vuelvan demasiado complicados y exóticos, como lo demuestran conceptos como "tipos fantasma" [11] y "tipos tambaleantes" [10]. Esto es como tratar de correr un maratón con una pelota y una cadena atadas a la pierna y gritar triunfante que casi lo logras a pesar de que saliste después de la primera milla. y por lo tanto, es necesariamente solo parcialmente e incompleto. Esto significa que los programas aún pueden salir mal debido a las propiedades que no son rastreadas por el verificador de tipo, y que hay programas que, aunque no pueden salir mal, no pueden verificarse. El impulso de hacer que la escritura estática sea menos parcial y más completa hace que los sistemas de tipos se vuelvan demasiado complicados y exóticos, como lo demuestran conceptos como "tipos fantasma" [11] y "tipos tambaleantes" [10]. Esto es como tratar de correr un maratón con una pelota y una cadena atadas a la pierna y gritar triunfante que casi lo logras a pesar de que saliste después de la primera milla. y que hay programas que, si bien no pueden salir mal, no pueden verificarse por tipo. El impulso de hacer que la escritura estática sea menos parcial y más completa hace que los sistemas de tipos se vuelvan demasiado complicados y exóticos, como lo demuestran conceptos como "tipos fantasma" [11] y "tipos tambaleantes" [10]. Esto es como tratar de correr un maratón con una pelota y una cadena atadas a la pierna y gritar triunfante que casi lo logras a pesar de que saliste después de la primera milla. y que hay programas que, si bien no pueden salir mal, no pueden verificarse por tipo. El impulso de hacer que la escritura estática sea menos parcial y más completa hace que los sistemas de tipos se vuelvan demasiado complicados y exóticos, como lo demuestran conceptos como "tipos fantasma" [11] y "tipos tambaleantes" [10]. Esto es como tratar de correr un maratón con una pelota y una cadena atadas a la pierna y gritar triunfante que casi lo logras a pesar de que saliste después de la primera milla.
Los defensores de los lenguajes escritos dinámicamente argumentan que el tipeo estático es demasiado rígido, y que la suavidad de los lenguajes dinámicamente los hace ideales para prototipos de sistemas con requisitos cambiantes o desconocidos, o que interactúan con otros sistemas que cambian de manera impredecible (integración de datos y aplicaciones). Por supuesto, los lenguajes escritos dinámicamente son indispensables para lidiar con el comportamiento verdaderamente dinámico del programa, como la intercepción de métodos, la carga dinámica, el código móvil, la reflexión en tiempo de ejecución, etc. En la madre de todos los documentos sobre scripting [16], John Ousterhout argumenta que los sistemas tipados estáticamente Los lenguajes de programación hacen que el código sea menos reutilizable, más detallado, no más seguro y menos expresivo que los lenguajes de secuencias de comandos escritos dinámicamente. Este argumento es repetido literalmente por muchos defensores de los lenguajes de secuencias de comandos de escritura dinámica. Argumentamos que esto es una falacia y cae en la misma categoría que argumentar que la esencia de la programación declarativa es eliminar la asignación. O como dice John Hughes [8], es una imposibilidad lógica hacer que un lenguaje sea más poderoso al omitir características. Defender el hecho de que retrasar toda verificación de tipos en tiempo de ejecución es una buena cosa, es jugar tácticas de avestruz con el hecho de que los errores deben detectarse lo antes posible en el proceso de desarrollo. Es una imposibilidad lógica hacer que un lenguaje sea más poderoso omitiendo funciones. Defender el hecho de que retrasar toda verificación de tipos en tiempo de ejecución es una buena cosa, es jugar tácticas de avestruz con el hecho de que los errores deben detectarse lo antes posible en el proceso de desarrollo. Es una imposibilidad lógica hacer que un lenguaje sea más poderoso omitiendo funciones. Defender el hecho de que retrasar toda verificación de tipos en tiempo de ejecución es una buena cosa, es jugar tácticas de avestruz con el hecho de que los errores deben detectarse lo antes posible en el proceso de desarrollo.
Los sistemas de tipo estático buscan eliminar ciertos errores estáticamente, inspeccionando el programa sin ejecutarlo e intentando demostrar la solidez en ciertos aspectos. Algunos sistemas de tipos pueden detectar más errores que otros. Por ejemplo, C # puede eliminar las excepciones de puntero nulo cuando se usa correctamente, mientras que Java no tiene ese poder. Twelf tiene un sistema de tipos que realmente garantiza que las pruebas terminarán , "resolviendo" el problema de detención .
Sin embargo, ningún sistema de tipos es perfecto. Para eliminar una clase particular de errores, también deben rechazar ciertos programas perfectamente válidos que violan las reglas. Esta es la razón por la que Twelf realmente no resuelve el problema de detención, simplemente lo evita arrojando una gran cantidad de pruebas perfectamente válidas que terminan de manera extraña. Del mismo modo, el sistema de tipos de Java rechaza la PersistentVector
implementación de Clojure debido al uso de matrices heterogéneas. Funciona en tiempo de ejecución, pero el sistema de tipos no puede verificarlo.
Por esa razón, la mayoría de los sistemas de tipos proporcionan "escapes", formas de anular el verificador estático. Para la mayoría de los lenguajes, estos toman la forma de conversión, aunque algunos (como C # y Haskell) tienen modos completos que están marcados como "inseguros".
Subjetivamente, me gusta la escritura estática. Implementado correctamente (pista: no Java), un sistema de tipo estático puede ser de gran ayuda para eliminar errores antes de que bloqueen el sistema de producción. Los lenguajes escritos dinámicamente tienden a requerir más pruebas unitarias, lo cual es tedioso en el mejor de los casos. Además, los lenguajes tipados estáticamente pueden tener ciertas características que son imposibles o inseguras en los sistemas de tipos dinámicos (me vienen a la mente las conversiones implícitas ). Todo es una cuestión de requisitos y gusto subjetivo. No construiría más el próximo Eclipse en Ruby de lo que trataría de escribir un script de respaldo en Assembly o parchear un kernel usando Java.
Ah, y las personas que dicen que " escribir x es 10 veces más productivo que escribir y " simplemente están echando humo. La escritura dinámica puede "sentirse" más rápido en muchos casos, pero pierde terreno una vez que realmente intentas ejecutar tu elegante aplicación . Del mismo modo, la escritura estática puede parecer la red de seguridad perfecta, pero un vistazo a algunas de las definiciones de tipo genérico más complicadas en Java hace que la mayoría de los desarrolladores se apresuren a usar anteojeras. Incluso con sistemas de tipos y productividad, no hay bala de plata.
Nota final: no se preocupe por el rendimiento al comparar la escritura estática con la dinámica. Los JIT modernos como V8 y TraceMonkey se acercan peligrosamente al rendimiento del lenguaje estático. Además, el hecho de que Java realmente se compila en un lenguaje intermedio intrínsecamente dinámico debería ser un indicio de que, en la mayoría de los casos, la escritura dinámica no es el gran factor que mata el rendimiento que algunas personas creen que es.
dadd
porque sabe de antemano que los operandos son double
s.
Bueno, ambos son muy, muy, muy mal entendidos y también dos cosas completamente diferentes. que no son mutuamente excluyentes .
Los tipos estáticos son una restricción de la gramática del lenguaje. Se puede decir estrictamente que los idiomas escritos estáticamente no están libres de contexto. La simple verdad es que resulta inconveniente expresar un lenguaje sensato en gramáticas libres de contexto que no tratan todos sus datos simplemente como vectores de bits. Los sistemas de tipo estático son parte de la gramática del lenguaje, si lo hay, simplemente lo restringen más de lo que podría hacerlo una gramática libre de contexto, por lo que las verificaciones gramaticales ocurren en dos pasos sobre la fuente realmente. Los tipos estáticos corresponden a la noción matemática de la teoría de tipos, la teoría de tipos en matemáticas simplemente restringe la legalidad de algunas expresiones. Como, no puedo decir 3 + [4,7]
en matemáticas, esto se debe a la teoría de los tipos.
Por lo tanto, los tipos estáticos no son una forma de "evitar errores" desde una perspectiva teórica, son una limitación de la gramática. De hecho, siempre que +, 3 e intervalos tengan las definiciones teóricas de conjunto habituales, si eliminamos el sistema de tipo 3 + [4,7]
tiene un resultado bastante bien definido que es un conjunto. Los "errores de tipo de tiempo de ejecución" no existen teóricamente, el uso práctico del sistema de tipos es evitar operaciones que para los seres humanos no tendrían sentido. Las operaciones siguen siendo solo el cambio y la manipulación de bits, por supuesto.
El problema con esto es que un sistema de tipos no puede decidir si tales operaciones van a ocurrir o no, si se permitiría su ejecución. Como en, particione exactamente el conjunto de todos los programas posibles en aquellos que van a tener un 'error de tipo' y aquellos que no. Solo puede hacer dos cosas:
1: probar que los errores de tipo van a ocurrir en un programa
2: probar que no van a ocurrir en un programa
Puede parecer que me estoy contradiciendo. Pero lo que hace un verificador de tipo C o Java es que rechaza un programa como 'no gramatical', o como lo llama 'error de tipo' si no puede tener éxito en 2. No puede probar que no van a ocurrir, eso no significa que no van a ocurrir, solo significa que no puede probarlo. Es muy posible que un programa que no tendrá un error de tipo sea rechazado simplemente porque el compilador no puede probarlo. Un simple ejemplo siendoif(1) a = 3; else a = "string";
, seguramente ya que siempre es cierto, la rama else nunca se ejecutará en el programa y no se producirá ningún error de tipo. Pero no puede probar estos casos de manera general, por lo que se rechaza. Esta es la principal debilidad de muchos lenguajes tipados estáticamente, para protegerte contra ti mismo, necesariamente también estás protegido en caso de que no lo necesites.
Pero, contrariamente a la creencia popular, también hay lenguajes estáticamente tipados que funcionan según el principio 1. Simplemente rechazan todos los programas de los que pueden demostrar que causará un error de tipo y pasan todos los programas que no pueden. Por lo tanto, es posible que permitan programas que tengan errores de tipo, un buen ejemplo es Typed Racket, es híbrido entre tipeo dinámico y estático. Y algunos dirían que obtienes lo mejor de ambos mundos en este sistema.
Otra ventaja del tipeo estático es que los tipos se conocen en tiempo de compilación y, por lo tanto, el compilador puede usar esto. Si en Java hacemos "string" + "string"
o 3 + 3
, ambos +
tokens en el texto al final representan una operación y un dato completamente diferentes, el compilador sabe cuál elegir entre los tipos solo.
Ahora, voy a hacer una declaración muy controvertida aquí, pero tengan paciencia conmigo: el 'tipeo dinámico' no existe .
Sonidos muy polémicos, pero es cierto, lenguajes dinámicamente se escriben desde una perspectiva teórica sin tipo . Son solo idiomas de tipo estático con un solo tipo. O simplemente, son lenguajes que en realidad son gramaticalmente generados por una gramática libre de contexto en la práctica.
¿Por qué no tienen tipos? Debido a que cada operación está definida y permitida en cada operante, ¿qué es exactamente un 'error de tipo de tiempo de ejecución'? Es de un ejemplo teórico puramente un efecto secundario . Si hacer lo print("string")
que imprime una cadena es una operación, entonces lo es length(3)
, la primera tiene el efecto secundario de escribir string
en la salida estándar, la segunda simplemente error: function 'length' expects array as argument.
, eso es todo. Desde una perspectiva teórica, no existe un lenguaje de tipo dinámico. No están tipificados
Muy bien, la ventaja obvia del lenguaje 'tipado dinámicamente' es el poder expresivo, un sistema de tipos no es más que una limitación del poder expresivo. Y, en general, los idiomas con un sistema de tipos de hecho tendrían un resultado definido para todas aquellas operaciones que no están permitidas si el sistema de tipos simplemente se ignora, los resultados simplemente no tendrían sentido para los humanos. Muchos idiomas pierden su integridad de Turing después de aplicar un sistema de tipos.
La desventaja obvia es el hecho de que pueden ocurrir operaciones que producirían resultados que no tienen sentido para los humanos. Para protegerse de esto, los lenguajes escritos dinámicamente suelen redefinir esas operaciones, en lugar de producir ese resultado absurdo, lo redefinen para tener el efecto secundario de escribir un error y posiblemente detener el programa por completo. Esto no es un "error" en absoluto, de hecho, la especificación del lenguaje generalmente implica esto, es tanto el comportamiento del lenguaje como imprimir una cadena desde una perspectiva teórica. Los sistemas de tipos obligan al programador a razonar sobre el flujo del código para asegurarse de que esto no suceda. O, de hecho, la razón por lo que se haceSuceder también puede ser útil en algunos puntos para la depuración, lo que demuestra que no es un "error" en absoluto, sino una propiedad bien definida del lenguaje. En efecto, el remanente único de 'tipeo dinámico' que tienen la mayoría de los idiomas está protegiendo contra una división por cero. Esto es lo que es la tipificación dinámica, no hay tipos, no hay más tipos que ese cero es un tipo diferente que todos los demás números. Lo que la gente llama 'tipo' es solo otra propiedad de un dato, como la longitud de una matriz o el primer carácter de una cadena. Y muchos idiomas escritos dinámicamente también le permiten escribir cosas como "error: the first character of this string should be a 'z'"
.
Otra cosa es que los idiomas escritos dinámicamente tienen el tipo disponible en tiempo de ejecución y, por lo general, pueden verificarlo, manejarlo y decidir a partir de él. Por supuesto, en teoría no es diferente a acceder al primer carácter de una matriz y ver de qué se trata. De hecho, puede hacer su propia C dinámica, solo use un tipo como long long int y use los primeros 8 bits para almacenar su 'tipo' y escribir funciones en consecuencia que lo comprueben y realicen la suma flotante o entera. Tiene un idioma de tipo estático con un tipo o un idioma dinámico.
En la práctica, todo esto muestra que los lenguajes tipados estáticamente se usan generalmente en el contexto de la escritura de software comercial, mientras que los lenguajes tipados dinámicamente tienden a usarse en el contexto de resolver algunos problemas y automatizar algunas tareas. Escribir código en idiomas estáticamente escritos simplemente lleva mucho tiempo y es engorroso porque no puede hacer cosas que sabe que van a salir bien, pero el sistema de tipos aún lo protege contra usted mismo por errores que no comete. Muchos programadores ni siquiera se dan cuenta de que lo hacen porque está en su sistema, pero cuando codificas en lenguajes estáticos, a menudo evitas el hecho de que el sistema de tipos no te permite hacer cosas que no pueden salir mal, porque No puedo demostrar que no saldrá mal.
Como señalé, "tipificado estáticamente" en general significa el caso 2, culpable hasta que se demuestre su inocencia. Pero algunos lenguajes, que no derivan su sistema de tipos de la teoría de tipos, utilizan la regla 1: Inocente hasta que se pruebe su culpabilidad, que podría ser el híbrido ideal. Entonces, tal vez Typed Racket es para ti.
Además, bueno, para un ejemplo más absurdo y extremo, actualmente estoy implementando un lenguaje donde los 'tipos' son realmente el primer carácter de una matriz, son datos, datos del 'tipo', 'tipo', que es en sí mismo un tipo y dato, el único dato que se tiene a sí mismo como tipo. Los tipos no son finitos o están limitados estáticamente, pero se pueden generar nuevos tipos en función de la información de tiempo de ejecución.
Quizás el mayor "beneficio" del tipeo dinámico es la curva de aprendizaje más superficial. No hay un sistema de tipos para aprender y no hay sintaxis no trivial para casos de esquina como restricciones de tipo. Eso hace que la escritura dinámica sea accesible para muchas más personas y sea factible para muchas personas para quienes los sistemas sofisticados de tipo estático están fuera del alcance. En consecuencia, la escritura dinámica se ha popularizado en los contextos de educación (por ejemplo, Scheme / Python en el MIT) y lenguajes específicos de dominio para no programadores (por ejemplo, Mathematica ). Los lenguajes dinámicos también se han popularizado en nichos donde tienen poca o ninguna competencia (por ejemplo, Javascript).
Los lenguajes de tipo dinámico más concisos (por ejemplo, Perl, APL, J, K, Mathematica ) son específicos del dominio y pueden ser significativamente más concisos que los lenguajes de tipo estático con fines generales más concisos (por ejemplo, OCaml ) en los nichos para los que fueron diseñados. .
Las principales desventajas del tipeo dinámico son:
Errores de tipo en tiempo de ejecución.
Puede ser muy difícil o incluso prácticamente imposible lograr el mismo nivel de corrección y requiere muchas más pruebas.
No hay documentación verificada por el compilador.
Rendimiento deficiente (generalmente en tiempo de ejecución pero a veces en tiempo de compilación, por ejemplo, Esquema de Stalin) y rendimiento impredecible debido a la dependencia de optimizaciones sofisticadas.
Personalmente, crecí con lenguajes dinámicos, pero no los tocaría con un poste de 40 'como profesional a menos que no hubiera otras opciones viables.
Del artículo de Artima's Typing: Strong vs. Weak, Static vs. Dynamic :
la escritura fuerte evita las operaciones de mezcla entre tipos no coincidentes. Para mezclar tipos, debe usar una conversión explícita
la escritura débil significa que puede mezclar tipos sin una conversión explícita
En el artículo de Pascal Costanza, Dynamic vs. Static Typing - A Pattern-Based Analysis (PDF), afirma que en algunos casos, la escritura estática es más propensa a errores que la escritura dinámica. Algunos lenguajes tipados estáticamente te obligan a emular manualmente el tipeo dinámico para hacer "Lo correcto". Se discute en Lambda the Ultimate .
Depende del contexto. Hay muchos beneficios que son apropiados para el sistema de tipo dinámico, así como para el tipo fuerte. Soy de la opinión de que el flujo del lenguaje de tipos dinámicos es más rápido. Los lenguajes dinámicos no están limitados por los atributos de clase y el compilador pensando en lo que está sucediendo en el código. Tienes un poco de libertad. Además, el lenguaje dinámico generalmente es más expresivo y da como resultado menos código, lo cual es bueno. A pesar de esto, es más propenso a errores, lo que también es cuestionable y depende más de la cobertura de pruebas unitarias. Es un prototipo fácil con lang dinámico, pero el mantenimiento puede convertirse en una pesadilla.
La principal ganancia sobre el sistema de tipo estático es el soporte IDE y seguramente el analizador de código estático. Te vuelves más seguro del código después de cada cambio de código. El mantenimiento es sencillo con tales herramientas.
Hay muchas cosas diferentes sobre los lenguajes estáticos y dinámicos. Para mí, la principal diferencia es que en lenguajes dinámicos las variables no tienen tipos fijos; en cambio, los tipos están vinculados a valores. Debido a esto, el código exacto que se ejecuta es indeterminado hasta el tiempo de ejecución.
En implementaciones tempranas o ingenuas, este es un gran obstáculo para el rendimiento, pero los JIT modernos se acercan tentadoramente a lo mejor que puede obtener con la optimización de compiladores estáticos. (en algunos casos marginales, incluso mejor que eso).
Se trata de la herramienta adecuada para el trabajo. Tampoco es mejor el 100% del tiempo. Ambos sistemas fueron creados por el hombre y tienen fallas. Lo siento, pero apestamos y hacemos cosas perfectas.
Me gusta la escritura dinámica porque se sale de mi camino, pero sí, pueden aparecer errores de tiempo de ejecución que no había planeado. Mientras que la escritura estática puede corregir los errores antes mencionados, pero enloquece a un programador novato (en lenguajes escritos) que intenta enloquecer entre un char constante y una cadena.
Escritura Estática: los lenguajes como Java y Scala son de escritura estática.
Las variables deben definirse e inicializarse antes de usarse en un código.
por ej. int x; x = 10;
System.out.println (x);
Typing Dinámico: Perl es un lenguaje de escritura dinámica.
No es necesario inicializar las variables antes de usarlas en el código.
y = 10; use esta variable en la parte posterior del código