Hay algunos problemas importantes que creo que se han perdido todas las respuestas existentes.
La escritura débil significa permitir el acceso a la representación subyacente. En C, puedo crear un puntero a caracteres, luego decirle al compilador que quiero usarlo como puntero a enteros:
char sz[] = "abcdefg";
int *i = (int *)sz;
En una plataforma little endian con enteros de 32 bits, esto se convierte i
en una matriz de números 0x64636261
y 0x00676665
. De hecho, incluso puede lanzar punteros a números enteros (del tamaño apropiado):
intptr_t i = (intptr_t)&sz;
Y, por supuesto, esto significa que puedo sobrescribir la memoria en cualquier parte del sistema. *
char *spam = (char *)0x12345678
spam[0] = 0;
* Por supuesto, los sistemas operativos modernos usan memoria virtual y protección de página, por lo que solo puedo sobrescribir la memoria de mi propio proceso, pero no hay nada en C que ofrezca tal protección, como cualquiera que haya codificado, por ejemplo, Classic Mac OS o Win16 puede decirte.
Lisp tradicional permitió tipos similares de piratería informática; en algunas plataformas, las celdas flotantes de doble palabra y las contras eran del mismo tipo, y podría pasar una a una función esperando la otra y "funcionaría".
La mayoría de los lenguajes de hoy en día no son tan débiles como lo fueron C y Lisp, pero muchos de ellos todavía son algo permeables. Por ejemplo, cualquier lenguaje OO que tenga un "downcast" no verificado, * es una filtración de tipo: esencialmente le está diciendo al compilador "Sé que no le di suficiente información para saber que esto es seguro, pero estoy bastante seguro es "cuando el objetivo de un sistema de tipos es que el compilador siempre tenga suficiente información para saber qué es seguro.
* Un downcast verificado no hace que el sistema de tipos de lenguaje sea más débil simplemente porque mueve la verificación al tiempo de ejecución. Si lo hiciera, entonces el polimorfismo de subtipo (también conocido como llamadas a funciones virtuales o completamente dinámicas) sería la misma violación del sistema de tipos, y no creo que nadie quiera decir eso.
Muy pocos lenguajes de "scripting" son débiles en este sentido. Incluso en Perl o Tcl, no puede tomar una cadena y simplemente interpretar sus bytes como un entero. * Pero vale la pena señalar que en CPython (y de manera similar para muchos otros intérpretes para muchos idiomas), si es realmente persistente, usted puede usar ctypes
para cargar libpython
, lanzar un objeto id
a un POINTER(Py_Object)
y forzar la fuga del sistema de tipos. Si esto debilita o no el sistema de tipos depende de sus casos de uso: si está tratando de implementar un entorno limitado de ejecución restringido en el idioma para garantizar la seguridad, tiene que lidiar con este tipo de escapes ...
* Puede usar una función como struct.unpack
leer los bytes y construir un nuevo int a partir de "cómo representaría C estos bytes", pero eso obviamente no tiene fugas; incluso Haskell lo permite.
Mientras tanto, la conversión implícita es realmente algo diferente de un sistema de tipo débil o con fugas.
Cada idioma, incluso Haskell, tiene funciones para, por ejemplo, convertir un número entero en una cadena o un flotante. Pero algunos lenguajes realizarán automáticamente algunas de esas conversiones; por ejemplo, en C, si llama a una función que quiere un float
y lo pasa int
, se convierte por usted. Esto definitivamente puede conducir a errores con, por ejemplo, desbordamientos inesperados, pero no son los mismos tipos de errores que se obtienen de un sistema de tipo débil. Y C no está siendo realmente más débil aquí; puedes agregar un int y un float en Haskell, o incluso concatenar un float en una cadena, solo tienes que hacerlo más explícitamente.
Y con lenguajes dinámicos, esto es bastante turbio. No hay tal cosa como "una función que quiera un flotador" en Python o Perl. Pero hay funciones sobrecargadas que hacen cosas diferentes con diferentes tipos, y hay una fuerte sensación intuitiva de que, por ejemplo, agregar una cadena a otra cosa es "una función que quiere una cadena". En ese sentido, Perl, Tcl y JavaScript parecen hacer muchas conversiones implícitas ( "a" + 1
le da "a1"
), mientras que Python hace mucho menos ( "a" + 1
genera una excepción, pero 1.0 + 1
le da 2.0
*). Es difícil poner ese sentido en términos formales: ¿por qué no debería haber un +
que tome una cadena y un int, cuando obviamente hay otras funciones, como la indexación, que sí?
* En realidad, en Python moderno, eso puede explicarse en términos de subtipo OO, ya que isinstance(2, numbers.Real)
es cierto. No creo que tenga sentido que 2
sea una instancia del tipo de cadena en Perl o JavaScript ... aunque en Tcl, en realidad lo es, ya que todo es una instancia de cadena.
Finalmente, hay otra definición, completamente ortogonal, de mecanografía "fuerte" frente a "débil", donde "fuerte" significa poderoso / flexible / expresivo.
Por ejemplo, Haskell le permite definir un tipo que es un número, una cadena, una lista de este tipo o un mapa de cadenas a este tipo, que es una manera perfecta de representar cualquier cosa que pueda decodificarse desde JSON. No hay forma de definir ese tipo en Java. Pero al menos Java tiene tipos paramétricos (genéricos), por lo que puede escribir una función que tome una Lista de T y saber que los elementos son de tipo T; otros lenguajes, como los primeros Java, lo obligaron a usar una Lista de objetos y bajarlos. Pero al menos Java le permite crear nuevos tipos con sus propios métodos; C solo te permite crear estructuras. Y BCPL ni siquiera tenía eso. Y así sucesivamente hasta el ensamblaje, donde los únicos tipos son diferentes longitudes de bits.
Entonces, en ese sentido, el sistema de tipos de Haskell es más fuerte que el de Java moderno, que es más fuerte que el de Java anterior, que es más fuerte que el de C, que es más fuerte que el de BCPL.
Entonces, ¿dónde encaja Python en ese espectro? Eso es un poco complicado. En muchos casos, la escritura de pato le permite simular todo lo que puede hacer en Haskell e incluso algunas cosas que no puede hacer; claro, los errores se detectan en tiempo de ejecución en lugar de tiempo de compilación, pero aún se detectan. Sin embargo, hay casos en los que la escritura de patos no es suficiente. Por ejemplo, en Haskell, puede decir que una lista vacía de entradas es una lista de entradas, por lo que puede decidir que reducir +
sobre esa lista debería devolver 0 *; en Python, una lista vacía es una lista vacía; no hay información de tipo que lo ayude a decidir qué reducción +
debería hacer.
* De hecho, Haskell no te deja hacer esto; Si llama a la función de reducción que no toma un valor de inicio en una lista vacía, obtendrá un error. Sin embargo, su sistema de tipos es lo suficientemente potente que podría hacer este trabajo, y Python no lo es.