¿Por qué los idiomas escritos dinámicamente no permiten que el desarrollador especifique el tipo?

14

Los lenguajes de tipo dinámico que conozco nunca permiten que los desarrolladores especifiquen los tipos de variables, o al menos tienen un soporte muy limitado para eso.

JavaScript, por ejemplo, no proporciona ningún mecanismo para imponer tipos de variables cuando sea conveniente hacerlo. PHP permite especificar algunos tipos de argumentos del método, pero no hay manera de utilizar los tipos nativos ( int, string, etc.) para los argumentos, y no hay manera de hacer cumplir tipos para otra cosa que argumentos.

Al mismo tiempo, sería conveniente tener la opción de especificar en algunos casos el tipo de una variable en un lenguaje de tipo dinámico, en lugar de hacer la verificación de tipo manualmente.

¿Por qué hay tal limitación? ¿Es por razones técnicas / de rendimiento (supongo que es en el caso de JavaScript), o solo por razones políticas (que es, creo, el caso de PHP)? ¿Es este el caso de otros idiomas de tipo dinámico con los que no estoy familiarizado?


Editar: siguiendo las respuestas y los comentarios, aquí hay un ejemplo para una aclaración: digamos que tenemos el siguiente método en PHP simple:

public function CreateProduct($name, $description, $price, $quantity)
{
    // Check the arguments.
    if (!is_string($name)) throw new Exception('The name argument is expected to be a string.');
    if (!is_string($description)) throw new Exception('The description argument is expected to be a string.');
    if (!is_float($price) || is_double($price)) throw new Exception('The price argument is expected to be a float or a double.');
    if (!is_int($quantity)) throw new Exception('The quantity argument is expected to be an integer.');

    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

Con algunos esfuerzos, esto puede reescribirse como (ver también Programación por contratos en PHP ):

public function CreateProduct($name, $description, $price, $quantity)
{
    Component::CheckArguments(__FILE__, __LINE__, array(
        'name' => array('value' => $name, 'type' => VTYPE_STRING),
        'description' => array('value' => $description, 'type' => VTYPE_STRING),
        'price' => array('value' => $price, 'type' => VTYPE_FLOAT_OR_DOUBLE),
        'quantity' => array('value' => $quantity, 'type' => VTYPE_INT)
    ));

    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

Pero el mismo método se escribiría de la siguiente manera si PHP aceptara opcionalmente tipos nativos para argumentos:

public function CreateProduct(string $name, string $description, double $price, int $quantity)
{
    // Check the arguments.
    if (!$name) throw new Exception('The name argument cannot be an empty string.');
    if ($price <= 0) throw new Exception('The price argument cannot be less or equal to zero.');
    if ($price < 0) throw new Exception('The price argument cannot be less than zero.');

    // We can finally begin to write the actual code.
    // TODO: Implement the method here.
}

¿Cuál es más corto de escribir? ¿Cuál es más fácil de leer?

Arseni Mourzenko
fuente
1
Opcionalmente, puede especificar tipos en algunos idiomas escritos dinámicamente, por ejemplo, en Common Lisp.
SK-logic
Unos cuantos idiomas escritos dinámicamente usan conversiones para forzar un tipo ...
Trezoid
Algunos lo hacen. Objective-C, por ejemplo, se escribe dinámicamente, pero puede declarar un tipo para variables y el compilador emitirá advertencias si no obtiene el tipo que espera.
mipadi
1
Clojure es un ejemplo de un lenguaje que normalmente se escribe dinámicamente pero opcionalmente puede dar tipos de variables a través de "sugerencias de tipo" (esto normalmente solo se hace donde sea necesario para obtener los beneficios de rendimiento de la información de tipo en tiempo de compilación)
mikera
1
Groovy es otro ejemplo de un lenguaje de tipo dinámico que permite especificar un tipo.
Eric Wilson

Respuestas:

17

El punto de tener una escritura estática es la capacidad de demostrar estáticamente que su programa es correcto con respecto a los tipos (nota: no es completamente correcto en todos los sentidos). Si tiene un sistema de tipo estático en todo momento, puede detectar errores de tipo la mayor parte del tiempo.

Si solo tiene información de tipo parcial, solo puede verificar las pequeñas partes de un gráfico de llamadas donde la información de tipo está completa. Pero ha dedicado tiempo y esfuerzo a especificar información de tipo para partes incompletas, donde no puede ayudarlo pero podría dar una falsa sensación de seguridad.

Para expresar información de tipo, necesita una parte del lenguaje que no puede ser excesivamente simple. Pronto se dará cuenta de que al igual que información intno es suficiente; querrá algo así List<Pair<Int, String>>, luego tipos paramétricos, etc. Puede ser bastante confuso incluso en el caso bastante simple de Java.

Luego, necesitará manejar esta información durante la fase de traducción y la fase de ejecución, porque es una tontería verificar solo los errores estáticos; el usuario esperará que las restricciones de tipo siempre se mantengan si se especifica. Los lenguajes dinámicos no son demasiado rápidos como son, y tales comprobaciones reducirán aún más el rendimiento. Un lenguaje estático puede gastar un gran esfuerzo para verificar los tipos porque solo lo hace una vez; Un lenguaje dinámico no puede.

Ahora imagine agregar y mantener todo esto solo para que las personas a veces usen opcionalmente estas características, solo detectando una pequeña fracción de errores de tipo. No creo que valga la pena el esfuerzo.

El objetivo de los lenguajes dinámicos es tener un marco muy pequeño y muy maleable, dentro del cual pueda hacer cosas mucho más complicadas cuando se hace en un lenguaje estático: varias formas de parches de mono que se utilizan para metaprogramación, burlas y pruebas, reemplazo dinámico de código, etc. Smalltalk y Lisp, ambos muy dinámicos, lo llevaron al extremo de enviar imágenes de entorno en lugar de construir desde la fuente. Pero cuando desee asegurarse de que las rutas de datos particulares sean seguras para los tipos, agregue aserciones y escriba más pruebas unitarias.

9000
fuente
1
+1, aunque las pruebas solo pueden mostrar que los errores no ocurren en ciertas situaciones. Son un mal reemplazo para una prueba de que los errores (de tipo) son imposibles.
Ingo
1
@Ingo: seguramente. Pero los lenguajes dinámicos son excelentes para retoques y prototipos rápidos, donde expresas ideas relativamente simples muy rápido. Si desea un código de producción a prueba de balas, puede recurrir a un lenguaje estático después, cuando haya extraído algunos componentes centrales estables.
9000
1
@ 9000, no dudo que sean geniales. Solo quería señalar que escribir 3 o 4 pruebas poco convincentes no es y no puede garantizar la seguridad de los tipos .
Ingo
2
@ 9000, cierto, y la mala noticia es que incluso entonces, es prácticamente imposible. Incluso el código Haskell o Agda se basa en suposiciones, como, por ejemplo, que la biblioteca que se usa en el tiempo de ejecución es correcta. Dicho esto, en un proyecto con unos 1000 LOC distribuidos en una docena de archivos de código fuente, es genial cuando puedes cambiar algo y sabes que el compilador apuntará a cada línea donde el cambio tiene un impacto.
Ingo
44
Las pruebas mal escritas no son un reemplazo para la verificación de tipo estático: son inferiores. Las pruebas bien escritas tampoco son un reemplazo para la verificación de tipo estático: son superiores.
Rein Henrichs
8

En la mayoría de los lenguajes dinámicos, al menos puede probar dinámicamente el tipo de un objeto o valor.

Y hay inferenciadores de tipo estático, verificadores y / o ejecutores para algunos lenguajes dinámicos: por ejemplo

Y Perl 6 admitirá un sistema de tipos opcional con escritura estática.


Pero supongo que la conclusión es que mucha gente usa dinámicamente lenguajes porque se escriben dinámicamente, y para ellos el tipeo estático opcional es muy "aburrido". Y muchas otras personas los usan porque son "fáciles de usar para los que no son programadores", en gran parte como consecuencia de la naturaleza dinámica de la indulgencia. Para ellos, la escritura opcional es algo que no entenderán o no se molestarán en usar.

Si fueras cínico, podrías decir que la escritura estática opcional ofrece lo peor de ambos mundos. Para un fanático de tipo estático, no evita todas las fallas de tipo dinámico. Para un ventilador de tipo dinámico, sigue siendo una chaqueta recta ... aunque con las correas no apretadas.

Stephen C
fuente
2
Cabe señalar que la mayoría de las comunidades desaprueban la verificación de los tipos en la mayoría de las circunstancias. Utilice el polimorfismo (específicamente, "tipear patos") al tratar con jerarquías de objetos, coercítese al tipo esperado si es posible / sensato. Quedan algunos casos en los que simplemente no tiene sentido permitir ningún tipo, pero en muchos idiomas se obtiene una excepción en la mayoría de estos casos, por lo que la verificación de tipos rara vez es útil.
44
"las personas suelen usar lenguajes dinámicamente porque se escriben dinámicamente" : JavaScript se usa porque es el único idioma que es compatible con la mayoría de los navegadores. PHP se usa porque es popular.
Arseni Mourzenko
2

Javascript planeó incluir algunos tipos de escritura estática opcionales, y parece que muchos lenguajes dinámicos maduros se dirigen de esa manera:

La razón es que cuando codifica por primera vez, desea ser rápido y dinámicamente escrito. Una vez que su código es sólido, funciona y tiene muchos usos (r), desea bloquear el diseño para reducir errores. (Esto es beneficioso tanto para los usuarios como para los desarrolladores, ya que los primeros verifican errores en sus llamadas y los segundos no rompen las cosas accidentalmente.

Tiene sentido para mí, ya que generalmente encuentro demasiada verificación de tipo al comienzo de un proyecto, muy poco al final de su vida útil, sin importar el idioma que use;).

Macke
fuente
No sé acerca de esos planes para incluir la escritura estática opcional en JavaScript; pero espero que no fueran tan atroces como eso en ActiveScript. lo peor de ambos JavaScript y Java.
Javier
Fue planeado para JS 4 (o ECMAscript 4) pero esa versión se abandonó debido a controversia. Estoy seguro de que algo similar aparecerá en el futuro, en algún idioma. (En Python puedes hacerlo con decoradores, por cierto)
Macke,
1
Los decoradores agregan verificación de tipo dinámico , una especie de afirmaciones. No puede obtener una comprobación exhaustiva de tipos estáticos en Python, por mucho que lo intente, debido al dinamismo extremo del lenguaje.
9000
@ 9000: Eso es correcto. Sin embargo, no creo que la verificación dinámica de tipos sea mala (pero preferiría las comparaciones de tipo de pato ala JS4), especialmente cuando se combina con pruebas unitarias, y la tipificación de decoradores podría ser más útil para admitir IDE / correctores de pelusa si fueran estandarizado.
Macke,
¡por supuesto que no está mal! Esto no es una cuestión de moralidad. En algún momento, el tipo debe ser "verificado" de una forma u otra. Si escribe * ((doble *) 0x98765E) en C, la CPU lo hará y comprobará si 0x98765E es realmente un puntero a un doble.
Ingo
2

Los objetos de Python hacen tener un tipo.

Usted especifica el tipo cuando crea el objeto.

Al mismo tiempo, sería conveniente tener la opción de especificar en algunos casos el tipo de una variable en un lenguaje de tipo dinámico, en lugar de hacer la verificación de tipo manualmente.

En realidad, una verificación de tipo manual en Python es casi siempre una pérdida de tiempo y código.

Es simplemente una mala práctica escribir código de verificación de tipo en Python.

Si un sociópata malicioso utilizó un tipo inapropiado, los métodos ordinarios de Python generarán una excepción ordinaria cuando el tipo no sea apropiado.

No escribes ningún código, tu programa todavía falla con a TypeError.

Hay casos muy raros en los que debe determinar el tipo en tiempo de ejecución.

¿Por qué hay tal limitación?

Como no es una "limitación", la pregunta no es una pregunta real.

S.Lott
fuente
2
"Los objetos de Python tienen un tipo". - ¿en serio? Al igual que los objetos perl, los objetos PHP y todos los demás elementos de datos del mundo. La diferencia entre la tipificación estática y dinámica es solo cuando se va a verificar el tipo, es decir, cuando los errores de tipo se manifiestan. Si aparecen como errores del compilador, es tipeo estático, si aparecen como errores de tiempo de ejecución, es dinámico.
Ingo
@Ingo: Gracias por la aclaración. El problema es que los objetos C ++ y Java se pueden convertir de un tipo a otro, lo que hace que el tipo de un objeto sea algo turbio y, por lo tanto, también la "comprobación de tipo" en esos compiladores sea un poco turbia. Donde la comprobación del tipo de Python, incluso si está en tiempo de ejecución, es mucho menos turbia. Además, la pregunta se acerca a decir que los idiomas escritos dinámicamente no tienen tipos. La buena noticia es que no comete ese error común.
S.Lott
1
Tienes razón, escribe conversiones (en contraste con las conversiones de tipo, es decir ((doble) 42)) subvierte la escritura estática. Son necesarios cuando el sistema de tipos no es lo suficientemente potente. Antes de Java 5, Java no tenía tipos parametrizados, no se podía vivir sin tipos de conversión. Hoy es mucho mejor, sin embargo, el sistema de tipos aún carece de tipos de clase superior, por no hablar de polimorfismo de mayor rango. Creo que es muy posible que los lenguajes de tipo dinámico disfruten de tantos seguidores precisamente porque liberan a uno de sistemas de tipos demasiado estrechos.
Ingo
2

La mayoría de las veces, no es necesario, al menos no al nivel de detalle que sugiere. En PHP, los operadores que usa dejan perfectamente claro lo que espera que sean los argumentos; Sin embargo, es un poco un descuido del diseño que PHP arrojará sus valores si es posible, incluso cuando pasa una matriz a una operación que espera una cadena, y debido a que la conversión no siempre es significativa, a veces se obtienen resultados extraños ( y esto es exactamente donde los controles de tipo son útiles). Aparte de eso, no importa si agrega enteros 1y / 5o cadenas "1"y "5"el mero hecho de que está utilizando+El operador le indica a PHP que desea tratar los argumentos como números, y PHP obedecerá. Una situación interesante es cuando recibe resultados de consultas de MySQL: muchos valores numéricos simplemente se devuelven como cadenas, pero no lo notará, ya que PHP los convierte por usted cada vez que los trata como números.

Python es un poco más estricto sobre sus tipos, pero a diferencia de PHP, Python ha tenido excepciones desde el principio y lo usa de manera consistente. El paradigma de "pedir perdón más fácil que permiso" sugiere simplemente realizar la operación sin verificación de tipo y confiar en que se genere una excepción cuando los tipos no tienen sentido. El único inconveniente de esto en lo que puedo pensar es que a veces, encontrarás que en algún lugar un tipo no coincide con lo que esperas que sea, pero encontrar la razón puede ser tedioso.

Y hay otra razón para considerar: los lenguajes dinámicos no tienen una etapa de compilación. Incluso si tiene restricciones de tipo, solo pueden dispararse en tiempo de ejecución, simplemente porque no hay tiempo de compilación . Si sus comprobaciones conducen a errores de tiempo de ejecución de todos modos, es mucho más fácil modelarlas en consecuencia: como comprobaciones explícitas (como is_XXX()en PHP o typeofJavaScript), o lanzando excepciones (como lo hace Python). Funcionalmente, tiene el mismo efecto (un error se señala en tiempo de ejecución cuando falla una verificación de tipo), pero se integra mejor con el resto de la semántica del lenguaje. Simplemente no tiene sentido tratar los errores de tipo fundamentalmente diferentes de otros errores de tiempo de ejecución en un lenguaje dinámico.

tdammers
fuente
0

Puede que le interese Haskell: su sistema de tipos infiere los tipos del código y también puede especificarlos.

daven11
fuente
55
Haskell es un gran idioma. Es de alguna manera un opuesto a los lenguajes dinámicos: pasas mucho tiempo describiendo tipos, y generalmente una vez que has calculado tus tipos, el programa funciona :)
9000
@ 9000: De hecho. Una vez que se compila, generalmente funciona. :)
Macke
@Macke - para diferentes valores de usualmente , por supuesto. :-) Para mí, el mayor beneficio del sistema de tipos y el paradigma funcional es, como señalé en otra parte, que uno no tiene que preocuparse si un cambio en alguna parte impacta silenciosamente algún código dependiente en otra parte: el compilador señalará errores de tipo y el estado mutable simplemente no existe.
Ingo
0

Como las otras respuestas han aludido, hay dos enfoques para escribir al implementar un lenguaje de programación.

  1. Haga que el programador le diga qué utilizan todas las variables y funciones para los tipos. Idealmente, también verifica que las especificaciones de tipo sean precisas. Luego, debido a que sabe qué tipo de cosas habrá en cada lugar, puede escribir código que suponga que las cosas apropiadas estarán allí y use cualquier estructura de datos que use para implementar ese tipo directamente.
  2. Adjunte un indicador de tipo a los valores que se almacenarán en variables y se pasarán y se devolverán de las funciones. Esto significa que el programador no tendrá que especificar ningún tipo de variables o funciones, porque los tipos realmente pertenecen a los objetos a los que se refieren las variables y funciones.

Ambos enfoques son válidos, y cuál usar depende en parte de consideraciones técnicas como el rendimiento, y en parte de razones políticas como el mercado objetivo para el idioma.

Larry Coleman
fuente
0

En primer lugar, los lenguajes dinámicos se han creado principalmente para facilitar su uso. Como ha mencionado, es realmente bueno tomar la conversión de tipo automáticamente y proporcionarnos menos gastos generales. Pero al mismo tiempo carece de problemas de rendimiento.

Puede seguir con los lenguajes dinámicos, en el caso de que no se preocupe por el rendimiento. Digamos, por ejemplo, que JavaScript se ejecuta más lentamente cuando necesita realizar muchas conversiones de tipos en su programa, pero ayuda a reducir la cantidad de líneas en su código.

Y para mencionar, hay otros lenguajes dinámicos incluso que permiten al programador especificar el tipo. Por ejemplo, Groovy es uno de los famosos lenguajes dinámicos que se ejecuta en JVM. Y ha sido muy famoso incluso en los últimos días. Tenga en cuenta que el rendimiento de Groovy es el mismo que el de Java.

Espero que te ayude.

Hormiga
fuente
-1

Simplemente no tiene sentido hacerlo.

¿Por qué?

Debido a que el sistema de tipos de DTL es precisamente tal que los tipos no se pueden determinar en tiempo de compilación. Por lo tanto, el compilador ni siquiera pudo verificar que el tipo especificado tendría sentido.

Ingo
fuente
1
¿Por qué? Tiene mucho sentido insinuar a un compilador qué tipos esperar. No contradecirá ninguna de las restricciones del sistema de tipos.
SK-logic
1
Lógica SK: si se escribe dinámicamente significa que cada función / método / operaciones toma objetos de tipo "Dinámico", "Cualquiera" o lo que sea y devuelve "Dinámico", "Cualquiera" lo que sea, en general no hay forma de decir que cierto valor siempre será un número entero, por ejemplo. Por lo tanto, el código de tiempo de ejecución debe buscar no enteros de todos modos, como si el tipo fuera "Dinámico" en primer lugar. Esto es exactamente lo que hace un sistema de tipo estático: permite probar que una determinada variable, campo o método de retorno siempre será de cierto tipo.
Ingo
@Ingo, no, hay una manera. Vea cómo se implementa en Common Lisp, por ejemplo. Es especialmente útil para las variables locales: puede aumentar drásticamente el rendimiento al introducir todas esas sugerencias de escritura.
SK-logic
@ SK-logic: ¿Quizás pueda decirme cuándo y cómo se detectan los errores de tipo en CL? De todos modos, @MainMa resumió el status quo bastante bien en su pregunta: es justo lo que uno podría esperar de lenguajes dinámicos "puramente".
Ingo
@Ingo, ¿qué te hace pensar que los tipos solo son útiles para probar estáticamente la corrección? No es cierto incluso para lenguajes como C, donde tienes una conversión de tipo sin marcar. Las anotaciones de tipo en lenguajes dinámicos son principalmente útiles como sugerencias de compilación que mejoran el rendimiento o especifican una representación numérica concreta. Estoy de acuerdo en que, en la mayoría de los casos, las anotaciones no deberían cambiar la semántica del código.
SK-logic
-1

Eche un vistazo a Go, en la superficie está estáticamente tipado, pero esos tipos pueden ser interfaces que son esencialmente dinámicas.

dan_waterworth
fuente