language agnostic - ¿Beneficios de la vida real de los lenguajes dinámicos?
language-agnostic dynamic-languages (9)
Estoy explorando varias posibilidades para desarrollar un nuevo sistema (aplicación web).
Soy un chico un poco "anticuado", orientado a objetos en la naturaleza (convertido de procedimiento hace muchos años). Jugué con Python y estudié un poco a Ruby, pero francamente me siento atraído por el uso de las herramientas de Microsoft (C #, ASP.NET MVC). Todo este tiempo de ejecución, sin errores de compilación en cosas básicas, etc., me complica la vida cuando se trata de crear grandes aplicaciones complejas.
Constantemente escucho a la gente hablar sobre las grandes cosas que puedes hacer con los lenguajes dinámicos, pero a excepción de los ejemplos con perros y gatos y la rapidez con la que puedes codificar una manera genial de contar cosas, la "fuerza industrial" de Visual Studio parece eliminarlas. Los pequeños y dinámicos lenguajes dinámicos ofrecen, especialmente ahora que tiene versiones Express gratuitas de VS y versiones completas disponibles de forma gratuita para empresas de nueva creación.
Siento que me estoy perdiendo algo aquí, porque las grandes aplicaciones se están desarrollando con lenguajes dinámicos, entonces, ¿cuáles son esas grandes cosas que estos lenguajes te permiten hacer cuando miras grandes aplicaciones complejas? ¿Qué puede hacerte regalar la fuerza de VS?
Mi experiencia
He trabajado con ambos, probablemente una década con cada profesional, en total.
Anecdotamente he pasado mucho más tiempo tratando de hacer que los lenguajes escritos de forma estática comprendan lo que quiero hacer (probablemente semanas, quizás meses en total) de lo que he pasado arreglando errores causados por errores de tipo dinámico (¿tal vez una o dos horas al año? ).
Estudios
La gente ha intentado bastante obtener evidencia de que uno u otro es mejor en términos de productividad del programador, pero la revisión más reciente que he leído dice que no hay evidencia sólida para ninguno de los dos.
Totalitario
El análisis de tipo estático es útil en algunas situaciones. No creo que deba ser inherentemente incorporado a su idioma. Debería ser una herramienta dentro de su entorno informático interactivo, junto con sus pruebas, su REPL, sus refactores, sus documentos, su codificación de alfabetización, etc.
Los sistemas de tipo estático pueden hacerte pensar en cosas útiles. Esto es especialmente cierto en un lenguaje como Haskell con Clases de Tipo y Mónadas (que confieso que todavía no entiendo). Esto es algo bueno, pero creo que es totalitario creer que siempre es algo bueno. Deberías pensarlo cuando sea apropiado. Un lenguaje no debe hacerte pensar en él o ser consciente de él desde el principio del desarrollo.
Tanto demasiado restrictivo como no suficientemente restrictivo
Los sistemas de tipo estático que no están completos de Turing tienen una expresividad limitada. ¿Por qué incluirlo en el idioma utilizando un nuevo idioma específico de dominio específico solo para hablar de tipos? Ahora su idioma establece el nivel exacto de expresividad al que tiene acceso. ¿Quieren más? Necesita volver a escribir su código o actualizar el idioma. ¿Quieres menos? No tienes suerte, usa un idioma diferente.
En su lugar, ¿por qué no usar un lenguaje dinámico básico para describir tipos y sistemas de tipos cuando lo desee? Como una biblioteca. Es más expresivo; más poderoso (si se desea); mas flexible; y significa un lenguaje base más pequeño.
Repetitivo
Los lenguajes tipificados estáticamente parecen fomentar la generación de códigos o calderas. No estoy seguro de si esto es inherente. Quizás un sistema macro suficientemente poderoso lo superaría. Estoy comparando el estado de los simulacros para probar en Swift con el del objetivo c.
Monolítico
Los lenguajes con tipos estáticos parecen fomentar las aplicaciones monolíticas: esta es una opinión y una observación que no puedo respaldar, pero parece que se sostiene ... Ellos, o las herramientas con las que vienen, parecen fomentar las aplicaciones y el pensamiento monolíticos.
Por el contrario, en los entornos informáticos interactivos no crea una nueva aplicación, en lugar de eso, extiende el sistema para que haga más. Los sistemas que conozco (máquinas Lisp, Smalltalk y Unix - sus herramientas tienen una interfaz de tipo dinámico entre ellos) utilizan la escritura dinámica para ensamblar partes.
Deberíamos construir pequeñas extensiones para un todo, en lugar de preparar nuevas aplicaciones completas, por lo que esta tendencia monolítica, si existe, es perjudicial.
Velocidad
Los compiladores JIT de rastreo dinámico más rápidos producen código rápido, y aún son una tecnología bastante joven. Aún así, también puedes hacer esto con un lenguaje estáticamente tipado.
A largo plazo
Sospecho que a largo plazo, terminaremos con entornos que admitan un potente análisis estático, donde podrá declarar tipos y protocolos, y el sistema lo ayudará a ver si están satisfechos, o lo ayudará a mostrarle lo implícito. tipos Pero no necesitarás hacer eso.
"qué tan rápido puedes codificar" me preocupa tanto que abandoné felizmente el lento y lento trabajo para compilar las cosas.
Ventajas de los lenguajes dinámicos.
Sin compilar, sin construir. Just Code and Test seguido de despliegue a producción.
Gratificación inmediata. No pasé mucho tiempo retorciéndome las manos por lo que podría ser una llamada a la API. Simplemente escríbalo interactivamente en el indicador de Python >>> y vea lo que realmente hace.
Ciclos de diseño muy, muy cortos. En lugar de diseñar cuidadosamente una jerarquía de clases con definiciones de interfaz de bonificación y declaraciones abstractas adecuadas y anulaciones, solo puedo codificar las clases, probarlas y terminar.
Menos código. La introspección dinámica del lenguaje reduce el volumen de la fuente. No escribo esto en mis aplicaciones; Dependo de frameworks para hacer esto por mi. Pero el código basado en framework es a menudo muy corto; no hay declaraciones duplicadas que sean tan comunes en Java, donde tiene que repetir cosas en una configuración XML.
No hay misterios. Como decimos en la comunidad de Python: "Usa la fuente, Luke". No hay ambigüedad en lo que hace un framework o lo que realmente significa una API.
Flexibilidad absoluta. A medida que cambian nuestros requisitos, no tenemos que luchar contra los cambios devastadores que rompen toda la arquitectura. Podemos, de manera trivial, hacer cambios en algunas clases porque el Duck Typing de Python elimina la necesidad de actualizar las definiciones de interfaz faltantes donde no creíamos que fueran necesarias. Simplemente no son ninguno; El código que no escribimos es un código que no tenemos que arreglar o mantener.
Resistencia. Cuando nuestros actuarios tienen un cerebro enloquecido, no tenemos que pasar meses pensando cómo integrar este nuevo y más sofisticado modelo de suscripción en las aplicaciones. Casi cualquier cosa puede ser comprimida. De nuevo, esto es estrictamente una consecuencia de la tipificación del pato. Nos hemos liberado de su ajuste forzoso en una arquitectura que no podía anticipar un nuevo modelo de negocio.
Dado que la fuente es la aplicación, la fuente puede ser su propio archivo de configuración. No tenemos archivos de configuración XML o INI en alguna sintaxis externa. Tenemos archivos de configuración en Python. El framework Django hace esto y seguimos su ejemplo. Tenemos declaraciones de datos simulados muy complejos para la demostración de ventas y pruebas de unidad. Los datos súper complejos son en realidad una colección de objetos de Python que hubieran provenido de una base de datos, excepto que omitimos cargar la base de datos. Es más simple simplemente modificar el constructor de objetos de Python en lugar de cargar una base de datos SQL.
[BTW. Después de más de 30 años de desarrollo de software en Cobol, Fortran, PL / I, Java, C, C ++, estoy cansado de la optimización manual relativamente baja que requieren la mayoría de los lenguajes compilados. Hace años, leí un comentario sobre la ineficiencia de la mayoría de los compiladores: nos lleva a crear sistemas de compilación elaborados para solucionar las limitaciones del compilador. Solo necesitamos make
porque cc
es muy lento.]
Editar
La programación dinámica no te convierte en un genio. Sólo ahorra mucho tiempo. Todavía tienes que gestionar el proceso de aprendizaje. Las cosas que no sabes son difíciles en todos los idiomas. Un lenguaje dinámico le brinda apalancamiento al permitirle avanzar de manera incremental, descubriendo una cosa nueva a la vez sin haber realizado un gran trabajo de diseño solo para encontrar que sus suposiciones eran incorrectas.
Si desea escribir una gran cantidad de código basado en una API mal entendida, entonces un lenguaje dinámico puede ayudar. Usted es libre de escribir una gran cantidad de código que se bloquea y quema en un lenguaje: C #, VB, C ++, Java o Python. Siempre puedes escribir código que no funcione.
El compilador le da una advertencia previa de que el código no funcionará. Típicamente, no compilar es una gran pista. Sin embargo, aún puede escribir una gran cantidad de código que compile y falle todas las pruebas unitarias. El compilador solo verifica la sintaxis, no la semántica.
Python puede darte una advertencia previa de que el código no funcionará. Por lo general, no se puede ejecutar de forma interactiva. Sin embargo, todavía puede escribir una gran cantidad de código que falla todas las pruebas de unidad.
Aquí hay partes de mi respuesta a una pregunta anterior similar ( sé C #. ¿Seré más productivo con Python? ):
Yo vengo de un fondo de C # / .NET yo mismo. Comenzó la programación en .NET en abt. 2001, y casi al mismo tiempo se introdujo a Python. En 2001, el tiempo que pasé en C # frente a Python fue de alrededor del 90% C # / 10% de Python. Ahora, la proporción es 5% C # / 95% Python. En mi empresa, todavía mantenemos una línea de productos basada en .NET. Pero todo lo nuevo está basado en Python.
Hemos creado aplicaciones no triviales en Python.
En mi experiencia, lo que me hace más productivo en Python vs. C # es:
- Es un lenguaje dinámico. El uso de un lenguaje dinámico a menudo le permite eliminar capas arquitectónicas completas de su aplicación. La naturaleza dinámica de Pythons le permite crear abstracciones de alto nivel reutilizables de manera más natural y flexible (sintaxis) de lo que puede en C #.
- Bibliotecas Las bibliotecas estándar y muchas de las bibliotecas de código abierto proporcionadas por la comunidad son de alta calidad. La gama de aplicaciones para las que se utiliza Python significa que la gama de bibliotecas es amplia.
- Ciclo de desarrollo más rápido. Ningún paso de compilación significa que puedo probar cambios más rápido. Por ejemplo, cuando se desarrolla una aplicación web, el servidor dev detecta cambios y vuelve a cargar la aplicación cuando se guardan los archivos. Ejecutar una prueba de unidad desde mi editor está a solo una pulsación de tecla y se ejecuta de forma instantánea.
- ''Fácil acceso'' a las funciones de uso frecuente: listas, listas de comprensión, generadores, tuplas, etc.
- Sintaxis menos verbosa. Puede crear un marco web Python basado en WSGI en menos líneas de código que su archivo .NET
web.config
típico :-) - Buena documentación. Buenos libros.
Considere el caso en el que tiene una subrutina que toma un solo argumento:
sub calculate( Int $x ){ ... }
Ahora sus requisitos cambian, y tiene que lidiar con múltiples argumentos:
multi sub calculate( Int $x ){ ... }
multi sub calculate( Int @x ){
my @ret;
for @x -> $x {
push @ret, calculate( $x );
}
return @ret;
}
Observe que hubo muy pocos cambios entre las diferentes versiones.
Y ahora, si descubriera que realmente debería haber estado usando números de punto flotante:
multi sub calculate( Num $x ){ ... }
multi sub calculate( Num @x ){
my @ret;
for @x -> $x {
push @ret, calculate( $x );
}
return @ret;
}
Ese fue un cambio más pequeño que antes, y tenga en cuenta que cualquier fragmento de código que llame a estas subrutinas continuará funcionando sin un solo cambio.
Estos ejemplos fueron escritos en Perl6
En general, prefiero hablar de lenguajes "interactivos" en lugar de lenguajes "dinámicos". Cuando solo tiene un ciclo de edición / compilación / ejecución, cualquier cambio lleva mucho tiempo. Bueno, al menos en el orden de "necesidad de guardar, compilar, verificar el informe de compilación, ejecutar la prueba, verificar los resultados de la prueba".
Con un lenguaje interactivo, generalmente es fácil modificar una pequeña parte y luego probar los resultados de inmediato. Si su prueba aún se demora un poco, no ha ganado mucho, pero por lo general puede hacer pruebas en casos más pequeños. Esto facilita el rápido desarrollo. Una vez que tenga una implementación correcta y conocida, esto también ayuda a la optimización, ya que puede desarrollar y probar sus funciones nuevas y mejoradas rápidamente y experimentar con diferentes representaciones o algoritmos.
La escritura estática es una forma de optimización prematura. Te obliga a tomar decisiones detalladas por adelantado, cuando es posible que no tengas el conocimiento para tomarlas. No ayuda especialmente la corrección del programa a menos que cree suficientes tipos para que tenga sentido lógico. Hace que sea difícil cambiar las estructuras de datos sobre la marcha.
Lo que se obtiene de esto es una cantidad muy limitada de verificación de corrección: muy limitada porque no separa las formas de uso de ints, por ejemplo. Supongamos que estamos tratando con filas y columnas; ambos son probablemente ints, pero las variables de fila y columna no deben usarse indistintamente. También obtienes optimización, que puede ser una cosa muy útil, pero no vale la pena ralentizar el desarrollo inicial para. Puede compensar la corrección de la corrección escribiendo las pruebas apropiadas.
El sistema de tipos Common Lisp es bueno para esto. Todos los objetos de datos conocen su tipo, y puede especificar ese tipo explícitamente si lo desea.
Un tipo de modelo de ejecución de bucle eval hace que sea muy fácil probar rutinas a medida que las escribe. No tiene que escribir explícitamente las pruebas por adelantado (aunque no hay nada que le impida hacerlo); puede escribirlos y ejecutarlos sobre la marcha (y luego puede refinarlos para convertirlos en un conjunto de pruebas; piénselo como un desarrollo de prueba incremental).
No tener un paso de compilación largo hace que sea más fácil realizar un desarrollo guiado por pruebas, porque es mucho más rápido ejecutar pruebas. No tiene que dividir lo que está haciendo cada vez que quiera hacer una prueba.
Cuando escucho a las personas quejarse de los lenguajes dinámicos, me acuerdo de las personas que se quejan de los sistemas de control de versiones sin bloqueos exclusivos. A algunas personas les lleva mucho tiempo darse cuenta de lo que ganan al pasar a un VCS moderno, e igualmente a algunas personas les toma mucho tiempo apreciar los idiomas dinámicos.
Me gusta escribir estática también. Pero no creo que lo extrañe tanto cuando escribo Python, (siempre y cuando las clases que estoy usando estén bien documentadas, y diría que ninguna característica del idioma nos salvará de la mala documentación) . Tampoco me pierdo la mayoría de las funciones dinámicas de Python cuando escribo C ++ (lambdas, echo de menos: trae C ++ 0x incluso si la sintaxis es horrible. Y enumera las comprensiones).
Para la detección de errores, la mayoría de los errores de "tipo incorrecto pasado" y "método incorrecto llamado" no son sutiles, por lo que la principal diferencia es que las excepciones reemplazan los errores del compilador. Debe asegurarse de ejecutar realmente todas las rutas de código y todas las rutas de datos importantes, pero, por supuesto, lo está haciendo de todos modos para proyectos serios. Sospecho que es raro que sea difícil probar todas las clases que podrían asignarse a una variable dada. El problema principal es que las personas acostumbradas a C ++ han aprendido a apoyarse en el compilador, y no se esfuerzan por evitar grandes clases de errores que el compilador detectará. En Python tienes la opción de pensar en ello mientras codificas, o esperar hasta que se ejecuten las pruebas para descubrirlo. Del mismo modo, la operación de "refactorización automática" de C ++ de "cambiar los parámetros y ver qué falla en la compilación" necesita alguna modificación en Python si desea localizar todos los sitios de llamadas.
Por lo tanto, debe asegurarse de que está ejecutando el subconjunto correcto de pruebas, ya que una ejecución completa de prueba de una aplicación Python grande tomará mucho más tiempo de lo que es aceptable en la fase de "compilación" de su código actual de C ++ / compile / code / compilar / codificar / compilar / test ciclo. Pero ese es exactamente el mismo problema que no querer reconstruir todo en C ++.
La escritura estática gana cuando en realidad es difícil ejecutar su código (por ejemplo, con dispositivos integrados o especialmente en entornos de servidor extraños que no puede reproducir localmente), ya que detecta más errores antes del momento en el que tiene problemas con los cables serie y rsync. Pero esa es la razón por la que desea un emulador, independientemente del idioma en el que está escribiendo, y por qué para un código de servidor serio, tiene un entorno de prueba adecuado si las máquinas de los desarrolladores no pueden simular la producción.
Además, vale la pena recordar que muchos de los errores y advertencias de los compiladores de C ++ no son en realidad sobre su diseño, sino por el hecho de que existen muchas formas diferentes de escribir código que parece correcto, pero se comporta de forma totalmente incorrecta. Los programadores de Python no necesitan advertir que han insertado accidentalmente un trigraph o un puntero de tipo, porque no tienen un preprocesador psicótico u optimizaciones basadas en un alias estricto.
Sé que ha pasado un tiempo, pero la respuesta aceptada enumera las propiedades que no se limitan a los idiomas tipificados dinámicamente. Por ejemplo, # 4 (menos código) también es cierto para Scala (si recuerdo correctamente). Es definitivamente cierto de Groovy (que está construido sobre Java, por lo que técnicamente groovy es estático y dinámico). El único con el que estoy de acuerdo es # 7 (resiliencia)
De wikipedia,
El lenguaje de programación dinámico es un ... lenguaje de programación de alto nivel que, en tiempo de ejecución, ejecuta muchos comportamientos de programación comunes que los lenguajes de programación estática realizan durante la compilación. Estos comportamientos podrían incluir la extensión del programa, agregando un nuevo código, extendiendo objetos y definiciones, o modificando el sistema de tipos. Programador dinámico también puede incluir escritura dinámica.
Desde mi experiencia, las ventajas de los lenguajes dinámicos son menos código / código más eficiente y mejores "mejores" prácticas (como una fuerte cultura de prueba) ... cosas que no son específicas de los lenguajes dinámicos.
Hablando de cultura de prueba, ciertos entusiastas del lenguaje dinámico (Ruby en particular) afirman que todas las pruebas que escriben (infección de prueba) les permite refactorizar las aplicaciones fácilmente. Tiendo a no comprar esta afirmación: las pruebas mal escritas (que son muy, muy fáciles de escribir) tienden a convertirse en una pesadilla de mantenimiento.
Para resumir: los lenguajes dinámicos tienden a generar una entrega rápida del producto, la aplicación puede o no ser fácil de mantener, pero son horribles para las aplicaciones de alto rendimiento (las aplicaciones compiladas estáticamente son mucho más rápidas)
Conchas interactivas! Esta es una gran ganancia de productividad. Simplemente inicie irb / python para sentarse frente al shell interactivo (para ruby y python respectivamente). Luego puede probar interactivamente sus clases, funciones, experimentar con expresiones (excelente para expresiones regulares), sintaxis y algoritmos diferentes. Este es un verdadero campo de juegos para programadores y una gran herramienta para la depuración también.
Solo yo dos centavos por los errores:
Todo este tiempo de ejecución, sin errores de compilación en cosas básicas, etc., me complica la vida cuando se trata de crear grandes aplicaciones complejas.
Los errores detectables del compilador son el tipo de errores que detectará cuando ejecute varias pruebas automáticas (unidad, funcional, etc.) y las que debería escribir de todos modos. Probablemente Linus Torvalds pueda decir: ¿ Pruebas de regresión? ¿Qué es eso? Si se compila, es bueno, si arranca es perfecto, pero tiene miles de evaluadores que harán el trabajo por él. Ah, y usando shells interactivos, obtendrás la retroalimentación automática sobre su sintaxis como cuando compila la aplicación pero el código se ejecuta en su lugar.