18

En cada proyecto que comencé en idiomas sin sistemas de tipo, eventualmente comencé a inventar un sistema tipo runtime. Tal vez el término "sistema de tipo" es demasiado fuerte; como mínimo, creo un conjunto de validadores de tipo/rango de valores cuando estoy trabajando con tipos de datos complejos, y luego siento la necesidad de ser paranoico sobre dónde se pueden crear y modificar los tipos de datos.¿Cómo se puede evitar crear un sistema de tipo ad-hoc en lenguajes de tipado dinámico?

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

No obstante, esto debe ser incorrecto. Siento como si no estuviera utilizando los lenguajes de tipo dinámico "correctamente". Si debo inventar un sistema de tipo y aplicarlo por mi cuenta, también puedo usar un lenguaje que tenga tipos para empezar.

Por lo tanto, mis preguntas son:

  • ¿Hay paradigmas de programación existentes (idiomas sin tipos) que evitan la necesidad de utilizar o inventar sistemas de tipo?
  • ¿Hay otras recomendaciones comunes sobre cómo resolver los problemas que resuelve el tipado estático en los lenguajes de tipo dinámico (sin reinventar tímidamente los tipos)?

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

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

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

Este tipo de datos se analiza comúnmente desde la entrada, por lo que para garantizar la entrada válida y un analizador correcto, los valores deben verificarse para la corrección de tipo y para 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 pueden comparar. Desde el erlang reference manual

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

habiendo pasado de mucho java a mucho groovy, resuelve el problema con pruebas unitarias, y solo acepta el hecho de que no sabe hasta el momento de ejecución el verdadero tipo de objeto. De hecho, el tipo verdadero de un objeto no importa si estás escribiendo patos. – dstarh

+2

Parece que está combinando tipos dinámicamente y con tipos débiles. Existe una distinción entre tipo fuertemente contra tipo débil y tipo estático frente a tipo dinámico. –

+1

Me interesaría ver un ejemplo del tipo de código que condujo a esta pregunta. –

Respuesta

1

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

Lo bueno es que puede personalizar su 'sistema de tipos' según sus necesidades, hacerlo flexible e inteligente, mientras que los sistemas tipo de lenguajes OO suelen tener características fijas. Cuando las estructuras de datos que utiliza son inmutables, una vez que haya validado dicha estructura, puede suponer que cumple con sus restricciones, al igual que con el tipado estático.

No tiene sentido estar listo para procesar cualquier tipo de datos en cualquier punto de un programa, 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.

2

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

En el mundo de Haskell, se oye mucho sofisticado, a veces hasta el punto de ser aterrador, teminología. Tipo de clases. Polimorfismo paramétrico Tipos de datos algebraicos generalizados. Escriba familias. Dependencias funcionales. El Ωmega programming language 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é es todo esto? Funciones añadidas a la tipificación estática para hacerlo 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. Aparte de la curva de aprendizaje, los sistemas tipo a menudo fallan al modelar elegantemente los problemas del mundo real. Un ejemplo particularmente bueno de esto es interactuar con otros idiomas (una motivación principal para C# 4's dynamic feature).

Los idiomas de tipado dinámico le dan la flexibilidad de implementar su propio marco de reglas y suposiciones sobre datos, en lugar de estar limitado por el sistema de tipo estático siempre limitado. Sin embargo, "su propio marco" no se verificará en la máquina, lo que significa que tiene la responsabilidad de garantizar que su "sistema de tipo" sea seguro y su código esté bien "tipeado".

Una de las cosas que he descubierto al aprender Haskell es que puedo llevar las lecciones aprendidas sobre tipeo fuerte y razonamiento de sonido a idiomas de tipado más débil, como C e incluso ensamblaje, y hacer la "verificación de tipo". A saber, puedo probar que las secciones de código son correctas en sí mismas, teniendo en cuenta las reglas que se supone que deben seguir mis funciones y valores, y las suposiciones que se me permiten hacer sobre otras funciones y valores. Cuando estoy depurando, reviso las cosas nuevamente, y pienso si mi enfoque es correcto o no.

La línea de fondo: tipado dinámico pone más flexibilidad al alcance de su mano. Por otro lado, los lenguajes de tipo estático tienden a ser más eficientes (en órdenes de magnitud), y los buenos sistemas de tipo estático reducen drásticamente el tiempo de depuración permitiéndole a la computadora hacer mucho por usted. Si desea los beneficios de ambos, instale un verificador de tipo estático en su cerebro aprendiendo idiomas decentes y fuertemente tipados.

+3

Estoy de acuerdo con el segundo punto, pero no con el primero. Las clases de tipos, GADT y FunDeps producen algo * más expresivo * que un típico lenguaje de tipado dinámico. En esencia, le permiten manipular contextos de clase * independientes * de valores tipeados individuales. No solo no puede hacer eso con los lenguajes estándar tipados dinámicamente, pero apenas tiene sentido pensar en ello. – sclv

7

Aparte de la confsion de estática vs vs dinámico y fuerte tipificación débil:

Lo que se quiere implementar en su ejemplo no es realmente resuelto por la mayoría de los sistemas de tipos estáticos existentes. Las comprobaciones de rango y las complicaciones como el 31 de febrero y especialmente las entradas analizadas generalmente se verifican durante el tiempo de ejecución, sin importar el tipo de sistema que tenga.

su ser en Erlang ejemplo, tengo un par de recomendaciones:

  • Use registros. Además de ser útil y servicial para un montón de razones, el dar un fácil comprobación de tipos en tiempo de ejecución y sin mucho esfuerzo ej .:

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

    esfuerzo sólo coincide con dos registros de fecha y hora. Incluso podría agregar guardias para verificar los rangos si la fuente no es de confianza. Y se ajusta a erlangs, deje que falle el método de manejo de errores: si no se encuentra una coincidencia, se obtiene una mala coincidencia, y puede manejar esto en el nivel donde sea apropiado (generalmente el nivel de supervisor).

  • Generalmente escribe el código que se bloquea cuando los supuestos no son válidos

  • Si este estática no se siente lo suficientemente comprobado: utilizar typer y dialyzer para encontrar el tipo de errores que se pueden encontrar de forma estática, lo los restos se verificarán en el tiempo de ejecución.

  • No sea demasiado restrictiva en sus funciones lo "tipos" que acepten, a veces la funcionalidad añadida de sólo hacer calle detrás útil incluso para diferentes entradas vale más que la comprobación de los tipos y varía en función de cada . Si lo haces donde realmente importa, detectarás el error lo suficientemente temprano para que sea fácil de reparar. Esto es especialmente cierto para un lenguaje funcional donde siempre sabe de dónde viene cada valor.

+0

Gracias, este es todo un buen consejo. Sin embargo, mi pregunta aún se mantiene en su segundo punto. A menudo, mis suposiciones no encajan bien en las expresiones de guardia, así que termino escribiendo funciones de verificación de tipo/rango y asegurando manualmente que se llamen en los momentos apropiados. Mi punto es que creo que es necesario inventar estos constructos de comprobación de tipo/validación y hacerlos valer en idiomas que no los tienen. Mi pregunta es si esta es la mejor/única manera, o si hay otros patrones y paradigmas que resuelven este conjunto de problemas de maneras más "naturales" para erlang y lenguajes similares. – drfloob

3

Un montón de buenas respuestas, quiero añadir:

¿Hay paradigmas de programación existentes (idiomas sin tipos) que evitan la necesidad de utilizar o inventar sistemas de tipo?

El paradigma más importante, especialmente en Erlang, es este: supongamos que el tipo es correcto; de lo contrario, deje que se bloquee. No escriba excesivamente el código paranoico de comprobación, pero suponga que la entrada que recibe es del tipo correcto o el patrón correcto. No escribir (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 a la última cláusula y tienen que choque inmediato. Deje que un supervisor y otros procesos hagan la limpieza (puede usar monitors() para los procesos de limpieza para saber cuándo se ha producido un bloqueo).

Do Sea preciso sin embargo. Escriba

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 un control en tiempo de ejecución, pero el objetivo es transmitir información al programador. Además, HiPE tiende a utilizar la pista para la optimización y eliminar la verificación de tipo si es posible. Por lo tanto, el precio puede ser menor de lo que piensas que es.

Elija un idioma sin tipo/de tipo dinámico, por lo que el precio que tiene que pagar es ese tipo de comprobación y los errores de los enfrentamientos ocurrirán en tiempo de ejecución. Como sugieren otros posts, un lenguaje estáticamente tipado no está exento de hacer algunos controles también - el sistema de tipo es (generalmente) una aproximación de una prueba de corrección. En la mayoría de los lenguajes estáticos a menudo obtienes información en la que no puedes 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: a partir de ahora, la cosa ha sido validada y podemos asumir ciertas cosas al respecto. El poder y la exactitud de esta suposición está directamente relacionada con su firma de tipo y lo bueno que es el programador para hacer malabarismos con los tipos estáticos del idioma.

¿Hay otras recomendaciones comunes sobre cómo resolver los problemas que resuelve el tipado estático en los lenguajes de tipo dinámico (sin reinventar los tipos tímidamente)?

Erlang tiene el dialyzer que se puede utilizar para analizar estáticamente e inferir tipos de sus programas. No va a llegar a tantos errores de tipo como un corrector de tipo en, por ejemplo, Ocaml, pero no se "Cry Wolf", ya sea: Un error del dializador es demostrablemente un error en el programa.Y no rechazará un programa que puede estar funcionando bien. Un ejemplo sencillo es:

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

La invocación and(true, greatmistake) volverá false, sin embargo, un sistema de tipo estático rechazará el programa porque va a deducir de la primera línea de que el tipo de firma 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 es necesario proteger a priori por un error. Si hay un error, el sistema de tiempo de ejecución tiene una verificación de tipo que lo capturará.

+0

Para aclarar, no es que "asumamos" tipos en erlang, debemos afirmarlos explícitamente. El problema es que tus ejemplos son todos muy simples; no es tan claro o fácil afirmar los tipos de datos "complejos" como en el ejemplo dado. – drfloob

1

Un lenguaje de tipos estáticos detecta errores de tipo en tiempo de compilación. Un lenguaje de tipeo dinámico los detecta en tiempo de ejecución. Existen algunas restricciones modestas sobre lo que se puede escribir en un lenguaje estáticamente tipado, de modo que todos los errores de tipo puedan capturarse en tiempo de compilación.

Pero sí, todavía tiene tipos incluso en un lenguaje de tipado dinámico, y eso es algo bueno. El problema es que vagas en lotes de los controles de tiempo de ejecución para asegurarse de que tiene los tipos que piensa que haces, ya que el compilador no ha tenido cuidado de que para usted.

Erlang tiene una herramienta muy buena para especificar y verificar estáticamente muchos tipos - dializador: Erlang type system, para referencias.

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

Y esto por sí solo no eliminará comprobaciones de rango, por desgracia. Sin muchas salsas especiales, tiene que hacer cumplir esto solo por convención (y constructores inteligentes, etc. para ayudar), o recurrir a comprobaciones del tiempo de ejecución, o ambas cosas.

+0

Si voy a tener que inventar constructores y mutadores, y hacer cumplir las convenciones estándar sobre su uso yo mismo (este ha sido el caso algunas veces), no puedo justificar el uso de un lenguaje que no tenga estos ya. Me encanta trabajar en Erlang, pero ¿qué beneficio hay si se dedican grandes cantidades de tiempo y códigos a reinventar y hacer cumplir lo que muchos otros idiomas le dan de forma gratuita? – drfloob

+0

@drfloop - Si el dializador no es suficiente para sus necesidades, entonces sí, ¡estoy absolutamente de acuerdo! ¡A Haskell! :-) – sclv

Cuestiones relacionadas