used the programming popular paid new most many languages language how exist best programming-languages functional-programming types erlang

programming-languages - popular - programming language of the future



¿Cómo se evita crear un sistema de tipos ad-hoc en idiomas tipificados dinámicamente? (5)

En cada proyecto que comencé en idiomas sin sistemas de tipos, eventualmente empiezo a inventar un sistema de tipos de tiempo de ejecución. Tal vez el término "sistema de tipos" es demasiado fuerte; como mínimo, creo un conjunto de validadores de rango de tipo / valor cuando trabajo con tipos de datos complejos, y luego siento la necesidad de estar paranoico sobre dónde se pueden crear y modificar los tipos de datos.

No lo había pensado dos veces hasta ahora. Como desarrollador independiente, mis métodos han estado trabajando en la práctica en varios proyectos pequeños, y no hay razón para que dejen de trabajar ahora.

Sin embargo, esto debe estar mal. Siento como si no estuviera usando los idiomas tipificados dinámicamente "correctamente". Si debo inventar un sistema de tipos y aplicarlo yo mismo, también puedo usar un lenguaje que tenga tipos para empezar.

Entonces, mis preguntas son:

  • ¿Existen paradigmas de programación existentes (para lenguajes sin tipos) que evitan la necesidad de usar o inventar sistemas de tipo?
  • ¿Existen recomendaciones comunes sobre cómo resolver los problemas que la tipificación estática resuelve en lenguajes de tipo dinámico (sin reinventar los tipos tímidamente)?

Aquí hay un ejemplo concreto para que usted considere. Estoy trabajando con fechas y zonas horarias en erlang (un lenguaje dinámico y fuertemente tipado). Este es un tipo de datos común con el que trabajo:

{{Y,M,D},{tztime, {time, HH,MM,SS}, Flag}}

... donde {Y,M,D} es una tupla que representa una fecha válida (todas las entradas son enteros), tztime y time son átomos, HH,MM,SS son enteros que representan un tiempo de 24 horas, y Flag es uno de los átomos u,d,z,s,w .

Este tipo de datos generalmente se analiza a partir de la entrada, por lo que para garantizar una entrada válida y un analizador correcto, es necesario verificar los valores para determinar la corrección del tipo y los rangos válidos. Más adelante, las instancias de este tipo de datos se comparan entre sí, lo que hace que el tipo de sus valores sea aún más importante, ya que todos los términos se comparan. Desde el manual de referencia de erlang.

number < atom < reference < fun < port < pid < tuple < list < bit string


A veces los datos necesitan validación. Validar los datos recibidos de la red es casi siempre una buena idea, especialmente los datos de una red pública. Ser paranoico aquí solo es bueno. Si algo parecido a un sistema de tipo estático ayuda a esto de la manera menos dolorosa, que así sea. Hay una razón por la que Erlang permite anotaciones de tipo. Incluso la coincidencia de patrones puede verse como un tipo de comprobación dinámica de tipos; Sin embargo, es una característica central del lenguaje. La estructura misma de los datos es su "tipo" en Erlang.

Lo bueno es que puede personalizar su ''sistema de tipo'' a la medida de sus necesidades, hacerlo flexible e inteligente, mientras que los sistemas de tipo de lenguajes OO normalmente tienen características fijas. Cuando las estructuras de datos que utiliza son inmutables, una vez que haya validado dicha estructura, está seguro de asumir que cumple con sus restricciones, al igual que con la escritura estática.

No tiene sentido estar listo para procesar cualquier tipo de datos en cualquier punto de un programa, ya sea de tipo dinámico o no. Un ''tipo dinámico'' es esencialmente una unión de todos los tipos posibles; Limitarlo a un subconjunto útil es una forma válida de programar.


Aparte de la confsión de escritura estática vs. dinámica y fuerte vs. débil:

Lo que quiere implementar en su ejemplo no se resuelve realmente con la mayoría de los sistemas de escritura estática existentes. Las verificaciones de rango y las complicaciones, como el 31 de febrero y especialmente las entradas analizadas, generalmente se verifican durante el tiempo de ejecución, independientemente del tipo de sistema que tenga.

Tu ejemplo de estar en Erlang tengo algunas recomendaciones:

  • Usa los registros. Además de ser útil y útil por un sinfín de razones, le ofrece una fácil comprobación del tipo de tiempo de ejecución sin mucho esfuerzo, por ejemplo:

    is_same_day(#datetime{year=Y1, month=M1, day=D1}, #datetime{year=Y2, month=M2, day=D2}) -> ...

    Sin esfuerzo solo coincide con dos registros de fecha y hora. Incluso podría agregar guardas para verificar los rangos si la fuente no es confiable. Y se ajusta a erlangs, que permite que se bloquee el método de manejo de errores: si no se encuentra una coincidencia, se obtiene una coincidencia, y puede manejarlo en el nivel en el que sea apropiado (generalmente el nivel de supervisor).

  • Generalmente escriba su código para que se bloquee cuando las suposiciones no son válidas

  • Si esto no se siente lo suficientemente comprobado como estático: use typer y dialyzer para encontrar el tipo de errores que se pueden encontrar de forma estática, cualquier resto quedará registrado en el tiempo de ejecución.

  • No sea demasiado restrictivo en sus funciones sobre los "tipos" que acepta, a veces la funcionalidad adicional de solo hacer algo útil incluso para diferentes entradas vale más que verificar los tipos y rangos en cada función. Si lo hace donde importa, generalmente detectará el error lo suficientemente temprano como para que sea fácil de corregir. Esto es especialmente cierto para un lenguaje funcional en el que siempre se sabe de dónde proviene cada valor.


Muchas buenas respuestas, permítanme agregar:

¿Existen paradigmas de programación existentes (para lenguajes sin tipos) que evitan la necesidad de usar o inventar sistemas de tipo?

El paradigma más importante, especialmente en Erlang, es este: suponga que el tipo es correcto, de lo contrario, deje que se bloquee. No escriba excesivamente el código paranoico de verificación, pero suponga que la entrada que obtiene es del tipo correcto o el patrón correcto. No escriba (hay excepciones a esta regla, pero en general)

foo({tag, ...}) -> do_something(..); foo({tag2, ...}) -> do_something_else(..); foo(Otherwise) -> report_error(Otherwise), try to fix problem here...

Mata la última cláusula y haz que se bloquee de inmediato. Deje que un supervisor y otros procesos realicen la limpieza (puede usar monitors() para que los procesos de limpieza sepan cuándo se produjo una falla).

Sea preciso sin embargo. Escribir

bar(N) when is_integer(N) -> ... baz([]) -> ... baz(L) when is_list(L) -> ...

Si se sabe que la función solo funciona con enteros o listas respectivamente. Sí, es una verificación de tiempo de ejecución, pero el objetivo es transmitir información al programador. Además, HiPE tiende a utilizar la sugerencia de optimización y elimina la verificación de tipo si es posible. Por lo tanto, el precio puede ser menor de lo que crees que es.

Usted elige un idioma no tipificado / dinámicamente, por lo que el precio que debe pagar es que la verificación de tipos y los errores de los choques ocurrirán en el tiempo de ejecución. Como lo sugieren otras publicaciones, un lenguaje estático no está exento de hacer algunas comprobaciones también; el sistema de tipos es (generalmente) una aproximación de una prueba de corrección. En la mayoría de los lenguajes estáticos, a menudo se obtiene información en la que no se puede confiar. Esta entrada se transforma en el "borde" de la aplicación y luego se convierte a un formato interno. La conversión sirve para marcar la confianza: de ahora en adelante, la cosa se ha validado y podemos asumir ciertas cosas al respecto. El poder y la exactitud de esta suposición están directamente vinculados a su tipo de firma y lo bueno que es el programador al hacer malabares con los tipos estáticos del lenguaje.

¿Existen recomendaciones comunes sobre cómo resolver los problemas que la tipificación estática resuelve en lenguajes de tipo dinámico (sin reinventar los tipos tímidamente)?

Erlang tiene el dialyzer que se puede usar para analizar estáticamente e inferir tipos de sus programas. No generará tantos errores de tipo como el verificador de tipos, por ejemplo, Ocaml, pero tampoco "llorará": un error del dializador es probablemente un error en el programa. Y no rechazará un programa que pueda estar funcionando bien. Un ejemplo simple es:

and(true, true) -> true; and(true, _) -> false; and(false, _) -> false.

La invocación and(true, greatmistake) devolverán false , sin embargo, un sistema de tipo estático rechazará el programa porque deducirá de la primera línea que la firma de tipo toma un valor booleano () como el segundo parámetro. El dializador aceptará esta función en contraste y le dará la firma (boolean (), term ()) -> boolean (). Puede hacerlo, porque no hay necesidad de proteger a priori para un error. Si hay un error, el sistema de tiempo de ejecución tiene una verificación de tipo que lo capturará.


Para que un lenguaje de tipo estático coincida con la flexibilidad de un lenguaje de tipo dinámico, creo que necesitaría muchas características, tal vez infinitas.

En el mundo de Haskell, se oye una gran cantidad de teminología sofisticada, a veces hasta temerosa. Clases de tipo. Polimorfismo paramétrico. Tipos de datos algebraicos generalizados. Tipo de familias. Dependencias funcionales. El lenguaje de programación Ωmega lo lleva aún más lejos, con el sitio web que enumera "funciones de nivel de tipo" y "polimorfismo de nivel", entre otros.

¿Qué son todos estos? Características añadidas a la escritura estática para hacerla más flexible. Estas características pueden ser realmente geniales y tienden a ser elegantes y alucinantes, pero a menudo son difíciles de entender. Dejando de lado la curva de aprendizaje, los sistemas tipográficos a menudo no modelan los problemas del mundo real con elegancia. Un ejemplo particularmente bueno de esto es interactuar con otros idiomas (una motivación importante para la función dynamic C # 4 ).

Los lenguajes de tipo dinámico le brindan la flexibilidad de implementar su propio marco de reglas y suposiciones sobre los datos, en lugar de estar restringidos por el sistema de tipo estático, siempre limitado. Sin embargo, "su propio marco" no será revisado por la máquina, lo que significa que la responsabilidad recae en usted para garantizar que su "sistema de tipos" sea seguro y que su código esté bien "escrito".

Una cosa que descubrí al aprender Haskell es que puedo transmitir las lecciones aprendidas sobre la tipificación sólida y el razonamiento sólido a lenguajes más débiles, como C e incluso ensamblaje, y hacer la "comprobación de tipos" por mí mismo. Es decir, puedo probar que las secciones del código son correctas en sí mismas, teniendo en cuenta las reglas que mis funciones y valores deben seguir, y las suposiciones que puedo hacer sobre otras funciones y valores. Al depurar, reviso y verifico las cosas otra vez, y pienso si mi enfoque es correcto o no.

La conclusión: la escritura dinámica pone más flexibilidad a su alcance . Por otro lado, los lenguajes de tipo estático tienden a ser más eficientes (por órdenes de magnitud), y los buenos sistemas de tipo estático reducen drásticamente el tiempo de depuración al permitir que la computadora haga mucho por ti. Si desea los beneficios de ambos, instale un comprobador de tipo estático en su cerebro aprendiendo idiomas decentes y fuertemente tipados.


Un lenguaje tipado estáticamente detecta errores tipográficos en el momento de la compilación. Un lenguaje tipado dinámicamente los detecta en tiempo de ejecución. Existen algunas restricciones modestas sobre lo que se puede escribir en un lenguaje de tipo estático, por lo que todos los errores de tipo pueden detectarse en el momento de la compilación.

Pero sí, todavía tienes tipos incluso en un lenguaje de tipo dinámico, y eso es bueno. El problema es que paseas por muchas comprobaciones de tiempo de ejecución para asegurarte de que tienes los tipos que crees que tienes, ya que el compilador no se ha ocupado de eso por ti.

Erlang tiene una muy buena herramienta para especificar y verificar de forma estática muchos tipos: dializador: sistema de tipo Erlang , para referencias.

Así que no reinvente los tipos, use las herramientas de escritura que Erlang ya proporciona, para manejar los tipos que ya existen en su programa (pero que aún no ha especificado).

Y esto por sí solo no eliminará los controles de rango, desafortunadamente. Sin mucha salsa especial, realmente tiene que imponer esto por su cuenta por convención (y constructores inteligentes, etc. para ayudar), o recurrir a los controles de tiempo de ejecución, o ambos.