En busca de aclaraciones sobre aparentes contradicciones con respecto a los idiomas de tipo débil

178

Creo que entiendo la escritura fuerte , pero cada vez que busco ejemplos de lo que es débil, termino encontrando ejemplos de lenguajes de programación que simplemente coaccionan / convierten tipos automáticamente.

Por ejemplo, en este artículo llamado Typing: Strong vs. Weak, Static vs. Dynamic dice que Python está fuertemente tipado porque obtienes una excepción si intentas:

Pitón

1 + "1"
Traceback (most recent call last):
File "", line 1, in ? 
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Sin embargo, tal cosa es posible en Java y en C #, y no los consideramos débilmente escritos solo por eso.

Java

  int a = 10;
  String b = "b";
  String result = a + b;
  System.out.println(result);

C#

int a = 10;
string b = "b";
string c = a + b;
Console.WriteLine(c);

En este otro artículo llamado Weakly Type Languages, el autor dice que Perl está tipeado débilmente simplemente porque puedo concatenar una cadena a un número y viceversa sin ninguna conversión explícita.

Perl

$a=10;
$b="a";
$c=$a.$b;
print $c; #10a

Entonces, el mismo ejemplo hace que Perl tenga un tipo débil, pero no Java y C # ?.

Vaya, esto es confuso ingrese la descripción de la imagen aquí

Los autores parecen implicar que un lenguaje que impide la aplicación de ciertas operaciones en valores de diferentes tipos está fuertemente tipado y lo contrario significa débilmente tipado.

Por lo tanto, en algún momento me sentí incitado a creer que si un idioma proporciona muchas conversiones automáticas o coerción entre tipos (como perl) puede terminar siendo considerado débilmente escrito, mientras que otros idiomas que proporcionan solo unas pocas conversiones pueden terminar siendo considerado fuertemente tipado.

Sin embargo, me inclino a creer que debo estar equivocado en esta interpretación, pero no sé por qué ni cómo explicarlo.

Entonces, mis preguntas son:

  • ¿Qué significa realmente que un idioma se tipee realmente débilmente?
  • ¿Podría mencionar algún buen ejemplo de tipeo débil que no esté relacionado con la conversión automática / coerción automática realizada por el idioma?
  • ¿Se puede escribir un idioma débilmente y al mismo tiempo?
Edwin Dalorzo
fuente
8
La escritura fuerte versus débil se trata de la conversión de tipos (¿de qué otra cosa podría tratarse?) Si desea un ejemplo de un lenguaje "muy" débil, mire esto: destroyallsoftware.com/talks/wat .
Wilduck
2
@Wildduck Todos los idiomas proporcionan conversiones de tipo, pero no todos se consideran de tipo débil. Mis ejemplos que se muestran a continuación demuestran cómo los programadores consideran un lenguaje débilmente tipado en base a los mismos ejemplos que son posibles en otros lenguajes considerados fuertemente tipados. Como tal, mi pregunta aún prevalece. ¿Cuál es la diferencia?
Edwin Dalorzo
1
La respuesta corta, creo, es que "Typedness" no es un estado binario. Java y C # están más fuertemente tipados pero no del todo.
Jodrell
3
Creo que esto es más adecuado para la ingeniería de software .
zzzzBov
44
@Brendan ¿Qué hay de sumar una carroza y un número entero? ¿No se convierte el número entero en un flotador en Python? ¿Diría ahora que Python no está totalmente tipeado?
Edwin Dalorzo

Respuestas:

210

ACTUALIZACIÓN: Esta pregunta fue el tema de mi blog el 15 de octubre de 2012. ¡ Gracias por la gran pregunta!


¿Qué significa realmente que un idioma se "tipee débilmente"?

Significa "este lenguaje usa un sistema de tipos que me parece desagradable". Un lenguaje "fuertemente tipado", por el contrario, es un lenguaje con un sistema de tipos que me parece agradable.

Los términos son esencialmente sin sentido y debe evitarlos. Wikipedia enumera once significados diferentes para "fuertemente tipado", varios de los cuales son contradictorios. Esto indica que las posibilidades de crear confusión son altas en cualquier conversación que implique el término "fuertemente tipado" o "débilmente tipado".

Todo lo que realmente se puede decir con certeza es que un lenguaje "fuertemente tipado" en discusión tiene alguna restricción adicional en el sistema de tipos, ya sea en tiempo de ejecución o en tiempo de compilación, que carece de un lenguaje "tipeado débilmente" en discusión. Lo que podría ser esa restricción no se puede determinar sin más contexto.

En lugar de utilizar "fuertemente tipeado" y "débilmente tipado", debe describir en detalle a qué tipo de seguridad de tipo se refiere. Por ejemplo, C # es un lenguaje de tipo estático y un lenguaje seguro de tipo y un lenguaje seguro de memoria , en su mayor parte. C # permite que se violen las tres formas de escritura "fuerte". El operador de conversión viola la escritura estática; le dice al compilador "Sé más sobre el tipo de tiempo de ejecución de esta expresión que tú". Si el desarrollador está equivocado, el tiempo de ejecución arrojará una excepción para proteger la seguridad de tipo. Si el desarrollador desea romper la seguridad de tipo o la seguridad de la memoria, puede hacerlo apagando el sistema de seguridad de tipo haciendo un bloque "inseguro". En un bloque inseguro, puede usar la magia de puntero para tratar un int como un flotante (violando la seguridad de tipo) o para escribir en la memoria que no posee. (Violando la seguridad de la memoria).

C # impone restricciones de tipo que se verifican tanto en tiempo de compilación como en tiempo de ejecución, lo que lo convierte en un lenguaje "fuertemente tipado" en comparación con los idiomas que realizan menos verificación en tiempo de compilación o menos tiempo de ejecución. C # también le permite, en circunstancias especiales, ejecutar una ejecución final en torno a esas restricciones, lo que lo convierte en un lenguaje "débilmente tipado" en comparación con los idiomas que no le permiten realizar dicha ejecución final.

¿Cuál es realmente? Es imposible decirlo; depende del punto de vista del hablante y su actitud hacia las diversas características del lenguaje.

Eric Lippert
fuente
14
@edalorzo: Se basa en el gusto y las opiniones personales sobre (1) qué aspectos de la teoría de tipos son relevantes y cuáles son irrelevantes, y (2) si se requiere un lenguaje para imponer o simplemente alentar las restricciones de tipo. Como señalé, uno podría decir razonablemente que C # está fuertemente tipado porque permite y fomenta el tipeo estático, y uno podría decir razonablemente que está tipeado débilmente porque permite la posibilidad de violar la seguridad del tipo.
Eric Lippert
44
@edalorzo: En cuanto a la asamblea, nuevamente, es una cuestión de opinión. Un compilador de lenguaje ensamblador no le permitirá mover un doble de 64 bits de la pila a un registro de 32 bits; le permitirá mover un puntero de 32 bits a un doble de 64 bits de la pila a un registro de 32 bits. En ese sentido, el lenguaje es "seguro de tipo": impone una restricción a la legalidad del programa basada en una clasificación de datos de tipo. Si esa restricción es "fuerte" o "débil" es una cuestión de opinión, pero es claramente una restricción.
Eric Lippert
2
Creo que ahora entiendo tu punto, un lenguaje verdaderamente débilmente tipado tendría que ser totalmente sin tipo o monotipado, lo que en la vida real es prácticamente imposible. Como tal, cualquier idioma tiene cierta definición de tipos, que son seguros, y dependiendo de la cantidad de agujeros que proporciona el lenguaje para violar o manipular sus datos o tipos de datos, puede terminar considerándolo más o menos débilmente escrito, tal vez incluso en solo ciertos contextos.
Edwin Dalorzo
77
@edalorzo: Correcto. Por ejemplo, el cálculo lambda sin tipo es casi tan débil como se puede obtener. Cada función es una función de una función a una función; cualquier dato se puede pasar a cualquier función sin restricción porque todo es del "mismo tipo". La validez de una expresión en cálculo lambda no tipificado depende solo de su forma sintáctica, no de un análisis semántico que clasifique ciertas expresiones como de ciertos tipos.
Eric Lippert
3
@Mark Le daría otro +1 por predecir que todos proporcionarían diferentes interpretaciones sobre el tema. Este "tipeo débil" parece ser un "concepto mítico" o una "leyenda urbana", todos lo han visto, pero nadie puede probar que existe :-)
Edwin Dalorzo
64

Como otros han señalado, los términos "fuertemente tipado" y "débilmente tipado" tienen tantos significados diferentes que no hay una respuesta única para su pregunta. Sin embargo, como mencionó específicamente a Perl en su pregunta, permítame tratar de explicarle en qué sentido Perl está tipeado débilmente.

El punto es que, en Perl, no existe una "variable entera", una "variable flotante", una "variable de cadena" o una "variable booleana". De hecho, por lo que el usuario puede (por lo general) decir, no son aún entero, flotante, cuerda o booleanas valores : todo lo que tienes son "escalares", que son todas estas cosas al mismo tiempo. Entonces puedes, por ejemplo, escribir:

$foo = "123" + "456";           # $foo = 579
$bar = substr($foo, 2, 1);      # $bar = 9
$bar .= " lives";               # $bar = "9 lives"
$foo -= $bar;                   # $foo = 579 - 9 = 570

Por supuesto, como notará correctamente, todo esto puede verse como solo coerción de tipo. Pero el punto es que, en Perl, los tipos siempre están obligados. De hecho, es bastante difícil para un usuario decir cuál es el "tipo" interno de una variable: en la línea 2 en mi ejemplo anterior, preguntando si el valor de $bares la cadena "9"o el número no 9tiene sentido, ya que, como en lo que respecta a Perl, son lo mismo . De hecho, es incluso posible que un escalar a Perl tiene internamente tanto una cadena y un valor numérico al mismo tiempo, como es por ejemplo el caso para $foodespués de la línea 2 anterior.

La otra cara de todo esto es que, dado que las variables de Perl no están tipificadas (o, más bien, no exponen su tipo interno al usuario), los operadores no pueden sobrecargarse para hacer cosas diferentes para diferentes tipos de argumentos; no puede simplemente decir "este operador hará X para números e Y para cadenas", porque el operador no puede (no quiere) decir qué tipo de valores son sus argumentos.

Así, por ejemplo, Perl tiene y necesita un operador de suma numérica ( +) y un operador de concatenación de cadenas ( .): como viste anteriormente, está perfectamente bien agregar cadenas ( "1" + "2" == "3") o concatenar números ( 1 . 2 == 12). Del mismo modo, los operadores de comparación numéricos ==, !=, <, >, <=, >=y <=>comparar los valores numéricos de sus argumentos, mientras que los operadores de comparación de cadenas eq, ne, lt, gt, le, gey cmplos comparan lexicográfico como cadenas. Entonces 2 < 10, pero 2 gt 10(pero "02" lt 10, mientras "02" == 2). (Eso sí, cierto otros idiomas, como JavaScript, intenta acomodar la escritura débil similar a Perl mientrastambién haciendo sobrecarga del operador. Esto a menudo conduce a la fealdad, como la pérdida de asociatividad para +).

(La mosca en el ungüento aquí es que, por razones históricas, Perl 5 tiene algunos casos de esquina, como los operadores lógicos bit a bit, cuyo comportamiento depende de la representación interna de sus argumentos. En general, se consideran un defecto de diseño molesto, ya que la representación interna puede cambiar por razones sorprendentes y, por lo tanto, predecir lo que hacen esos operadores en una situación determinada puede ser complicado).

Dicho todo esto, se podría argumentar que Perl hace tener tipos fuertes; simplemente no son el tipo de tipos que podrías esperar. Específicamente, además del tipo "escalar" discutido anteriormente, Perl también tiene dos tipos estructurados: "matriz" y "hash". Esos son muy distintos de los escalares, hasta el punto en que las variables de Perl tienen diferentes sigilos que indican su tipo ( $para escalares, @para matrices, %para hashes) 1 . No son reglas de conversión entre estos tipos, por lo que puede escribir por ejemplo %foo = @bar, pero muchos de ellos son bastante con pérdidas: por ejemplo, $foo = @barasigna la longitud de la matriz @barde$foo, no su contenido. (Además, hay algunos otros tipos extraños, como los globos tipográficos y las manijas de E / S, que a menudo no se ven expuestos).

Además, una pequeña grieta en este diseño agradable es la existencia de tipos de referencia, que son un tipo especial de escalares (y que se pueden distinguir de los escalares normales, utilizando el refoperador). Es posible usar referencias como escalares normales, pero sus valores de cadena / numéricos no son particularmente útiles, y tienden a perder su referencia especial si los modifica utilizando operaciones escalares normales. Además, cualquier variable Perl 2 se puede blesseditar en una clase, convirtiéndola en un objeto de esa clase; el sistema de clases OO en Perl es algo ortogonal al sistema de tipo primitivo (o falta de tipo) descrito anteriormente, aunque también es "débil" en el sentido de seguir la escritura del patoparadigma. La opinión general es que, si te encuentras comprobando la clase de un objeto en Perl, estás haciendo algo mal.


1 En realidad, el sigilo denota el tipo de valor al que se accede, de modo que, por ejemplo, @foose denota el primer escalar de la matriz $foo[0]. Ver perlfaq4 para más detalles.

A los objetos en Perl se accede (normalmente) a través de referencias a ellos, pero lo que realmente se blessedita es la variable (posiblemente anónima) a la que apunta la referencia. Sin embargo, la bendición es de hecho una propiedad de la variable, no de su valor, por lo que, por ejemplo, asignar la variable bendecida real a otra simplemente le proporciona una copia superficial y sin bendiciones. Ver perlobj para más detalles.

Ilmari Karonen
fuente
19

Además de lo que Eric ha dicho, considere el siguiente código C:

void f(void* x);

f(42);
f("hello");

A diferencia de los lenguajes como Python, C #, Java o lo que sea, lo anterior está mal escrito porque perdemos información de tipo. Eric señaló correctamente que en C # podemos eludir el compilador lanzando, efectivamente diciéndole "Sé más sobre el tipo de esta variable que tú".

¡Pero incluso entonces, el tiempo de ejecución seguirá comprobando el tipo! Si el lanzamiento no es válido, el sistema de tiempo de ejecución lo detectará y generará una excepción.

Con la eliminación de tipos, esto no sucede: la información de tipos se desecha. Un yeso void*en C hace exactamente eso. En este sentido, lo anterior es fundamentalmente diferente de una declaración de método C # como void f(Object x).

(Técnicamente, C # también permite el borrado de tipo a través de código inseguro o clasificación).

Esto es tan débilmente escrito como se pone. Todo lo demás es solo una cuestión de verificación de tipo estático versus dinámico, es decir, del momento en que se verifica un tipo.

Konrad Rudolph
fuente
1
+1 Buen punto, ahora me hiciste pensar en el borrado de texto como una característica que también puede implicar "tipeo débil". También hay borrado de tipos en Java, y en tiempo de ejecución, el sistema de tipos le permitirá violar restricciones que el compilador nunca aprobaría. El ejemplo de C es excelente para ilustrar el punto.
Edwin Dalorzo
1
De acuerdo, hay capas para la cebolla, o el infierno. Parece una definición más significativa de debilidad de tipo.
Jodrell
1
@edalorzo No creo que esto es bastante la misma ya que a pesar de Java le permite eludir el compilador, el sistema de tipos en tiempo de ejecución todavía se captura la violación. Por lo tanto, el sistema de tipo de tiempo de ejecución Java está fuertemente tipado a este respecto (hay excepciones, por ejemplo, donde la reflexión se puede utilizar para eludir el control de acceso).
Konrad Rudolph
1
@edalorzo Solo puede eludir el compilador de esta manera, no el sistema de tiempo de ejecución. Es importante darse cuenta de que los lenguajes como Java y C # (y en cierta medida también C ++) tienen un sistema de tipos que se garantiza dos veces: una en tiempo de compilación y otra en tiempo de ejecución. void*rompe a través de ambos controles de tipo. El borrado de tipo genérico no lo hace, solo evita las verificaciones en tiempo de compilación. Es exactamente como los moldes explícitos (mencionados por Eric) a este respecto.
Konrad Rudolph
1
@edalorzo Re su confusión: no deberíamos. La distinción es fluida. Y sí, el borrado de tipos hace que Java se tipee débilmente a este respecto. Mi punto era que incluso con el borrado de tipo genérico aún no puede eludir las verificaciones de tipo de tiempo de ejecución a menos que también use la reflexión .
Konrad Rudolph
14

Un ejemplo perfecto proviene del artículo de Wikipedia de Strong Typing :

En general, la tipificación fuerte implica que el lenguaje de programación impone restricciones severas en la mezcla que se permite que ocurra.

Mecanografía débil

a = 2
b = "2"

concatenate(a, b) # returns "22"
add(a, b) # returns 4

Typing fuerte

a = 2
b = "2"

concatenate(a, b) # Type Error
add(a, b) # Type Error
concatenate(str(a), b) #Returns "22"
add(a, int(b)) # Returns 4

Tenga en cuenta que un lenguaje de escritura débil puede mezclar diferentes tipos sin errores. Un lenguaje de tipo fuerte requiere que los tipos de entrada sean los tipos esperados. En un lenguaje de tipo fuerte, un tipo se puede convertir ( str(a)convierte un entero en una cadena) o emitir ( int(b)).

Todo esto depende de la interpretación de la escritura.

SaulBack
fuente
3
Pero esto lleva a los ejemplos contradictorios proporcionados en la pregunta. Un lenguaje fuertemente tipado puede incluir una coerción implícita que significa que uno (o ambos) de sus dos ejemplos de "Error de tipo" se convierten automáticamente al relevante de los dos ejemplos siguientes, pero en general ese lenguaje sigue siendo fuertemente tipado.
Mark Hurd
3
Cierto. Supongo que se podría decir que hay diversos grados de escritura fuerte y escritura débil. La conversión implícita podría significar que el idioma está tipado con menos fuerza que un idioma que no realiza la conversión implícita.
SaulBack
4

Me gustaría contribuir a la discusión con mi propia investigación sobre el tema, ya que otros comentan y contribuyen. He estado leyendo sus respuestas y siguiendo sus referencias y he encontrado información interesante. Como se sugirió, es probable que la mayoría de esto se discuta mejor en el foro de Programadores, ya que parece ser más teórico que práctico.

Desde un punto de vista teórico, creo que el artículo de Luca Cardelli y Peter Wegner titulado Sobre comprensión de tipos, abstracción de datos y polimorfismo tiene uno de los mejores argumentos que he leído.

Un tipo puede verse como un conjunto de ropa (o una armadura) que protege una representación subyacente sin tipo de uso arbitrario o no intencionado. Proporciona una cubierta protectora que oculta la representación subyacente y limita la forma en que los objetos pueden interactuar con otros objetos. En un sistema sin tipo, los objetos sin tipo están desnudos porque la representación subyacente está expuesta para que todos la vean. Violar el sistema de tipos implica quitarse el conjunto de ropa protectora y operar directamente sobre la representación desnuda.

Esta afirmación parece sugerir que escribir débilmente nos permitiría acceder a la estructura interna de un tipo y manipularlo como si fuera otra cosa (otro tipo). Quizás lo que podríamos hacer con un código inseguro (mencionado por Eric) o con punteros borrados de tipo c mencionados por Konrad.

El artículo continúa ...

Los idiomas en los que todas las expresiones son coherentes con los tipos se denominan idiomas fuertemente tipados. Si un idioma está fuertemente tipado, su compilador puede garantizar que los programas que acepta se ejecutarán sin errores de tipo. En general, debemos esforzarnos por una escritura fuerte y adoptar una escritura estática siempre que sea posible. Tenga en cuenta que cada idioma estáticamente tipado está fuertemente tipado, pero lo contrario no es necesariamente cierto.

Como tal, la escritura fuerte significa la ausencia de errores de escritura, solo puedo suponer que la escritura débil significa lo contrario: la probable presencia de errores de escritura. ¿En tiempo de ejecución o tiempo de compilación? Parece irrelevante aquí.

Lo gracioso, según esta definición, un lenguaje con poderosas coacciones de tipo como Perl se consideraría fuertemente tipado, porque el sistema no falla, pero se trata de los tipos coercitándolos en equivalencias apropiadas y bien definidas.

Por otro lado, podría decir que la asignación de ClassCastExceptiony ArrayStoreException(en Java) y InvalidCastException, ArrayTypeMismatchException(en C #) que indicaría un nivel de mecanografía débilmente, al menos en tiempo de compilación? La respuesta de Eric parece estar de acuerdo con esto.

En un segundo artículo llamado Programful Typeful Programming proporcionado en una de las referencias proporcionadas en una de las respuestas en esta pregunta, Luca Cardelli profundiza en el concepto de violaciones de tipo:

La mayoría de los lenguajes de programación del sistema permiten violaciones de tipo arbitrario, algunas indiscriminadamente, otras solo en partes restringidas de un programa. Las operaciones que involucran violaciones de tipo se denominan poco sólidas. Las infracciones de tipo se dividen en varias clases [entre las cuales podemos mencionar]:

Coacciones de valor básico : Estas incluyen conversiones entre enteros, booleanos, caracteres, conjuntos, etc. No hay necesidad de violaciones de tipo aquí, ya que se pueden proporcionar interfaces integradas para llevar a cabo las coerciones de una manera de sonido de tipo.

Como tal, las coacciones de tipo como las proporcionadas por los operadores podrían considerarse violaciones de tipo, pero a menos que rompan la consistencia del sistema de tipo, podríamos decir que no conducen a un sistema de tipo débil.

En base a esto, ni Python, Perl, Java o C # están mal escritos.

Cardelli menciona dos tipos de opiniones que considero muy bien casos de mecanografía realmente débil:

Dirección aritmética. Si es necesario, debe haber una interfaz integrada (poco sólida) que proporcione las operaciones adecuadas en las direcciones y las conversiones de tipo. Varias situaciones involucran punteros en el montón (muy peligroso con la reubicación de colectores), punteros a la pila, punteros a áreas estáticas y punteros a otros espacios de direcciones. A veces, la indexación de matrices puede reemplazar la aritmética de direcciones. Mapeo de memoria. Esto implica mirar un área de memoria como una matriz no estructurada, aunque contiene datos estructurados. Esto es típico de los asignadores y recolectores de memoria.

Este tipo de cosas posibles en lenguajes como C (mencionado por Konrad) o mediante código inseguro en .Net (mencionado por Eric) realmente implicaría escribir débilmente.

Creo que la mejor respuesta hasta ahora es la de Eric, porque la definición de estos conceptos es muy teórica, y cuando se trata de un lenguaje en particular, las interpretaciones de todos estos conceptos pueden llevar a diferentes conclusiones discutibles.

Edwin Dalorzo
fuente
4

Una escritura débil significa que un alto porcentaje de tipos puede ser coaccionado implícitamente, intentando adivinar qué pretendía el codificador.

La tipificación fuerte significa que los tipos no están obligados, o al menos están menos obligados.

La tipificación estática significa que los tipos de sus variables se determinan en tiempo de compilación.

Muchas personas recientemente han estado confundiendo "mecanografiado manifiestamente" con "mecanografiado fuertemente". "Tipo de manifiesto" significa que declara los tipos de sus variables explícitamente.

Python está en su mayoría fuertemente tipado, aunque puede usar casi cualquier cosa en un contexto booleano, y los booleanos se pueden usar en un contexto entero, y puede usar un entero en un contexto flotante. No se escribe de manera manifiesta, porque no necesita declarar sus tipos (excepto Cython, que no es completamente python, aunque interesante). Tampoco está tipificado estáticamente.

C y C ++ están expresamente escritos, estáticamente escritos y algo fuertemente tipados, porque declaras tus tipos, los tipos se determinan en tiempo de compilación, y puedes mezclar enteros y punteros, o enteros y dobles, o incluso lanzar un puntero a un tipo en un puntero a otro tipo.

Haskell es un ejemplo interesante, porque no está escrito de forma manifiesta, sino que también está escrito de forma estática y fuerte.

usuario1277476
fuente
+1 Porque me gusta el término acuñado "manifiestamente tipado", que clasifica los lenguajes como Java y C #, donde debe declarar explícitamente los tipos y distinguirlos de otros lenguajes de tipo estático como Haskell y Scala, donde la inferencia de tipos juega un papel importante y esto normalmente confunde a las personas, como usted dice, y les hace creer que estos idiomas se escriben dinámicamente.
Edwin Dalorzo
3

La tipificación fuerte <=> débil no se trata solo del continuo sobre cuánto o cuán poco de los valores son coaccionados automáticamente por el idioma para un tipo de datos a otro, sino cuán fuerte o débilmente se escriben los valores reales . En Python y Java, y principalmente en C #, los valores tienen sus tipos establecidos en piedra. En Perl, no tanto: en realidad solo hay un puñado de diferentes tipos de valores para almacenar en una variable.

Abramos los casos uno por uno.


Pitón

En el ejemplo de Python 1 + "1", el +operador llama al __add__tipo para intdarle la cadena "1"como argumento; sin embargo, esto da como resultado NotImplemented:

>>> (1).__add__('1')
NotImplemented

A continuación, el intérprete prueba el __radd__de str:

>>> '1'.__radd__(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__radd__'

Como falla, el +operador falla con el resultado TypeError: unsupported operand type(s) for +: 'int' and 'str'. Como tal, la excepción no dice mucho sobre la escritura fuerte, pero el hecho de que el operador + no coaccione sus argumentos automáticamente al mismo tipo, es un indicador del hecho de que Python no es el lenguaje más débilmente escrito en el continuo.

Por otro lado, en Python 'a' * 5 se implementa:

>>> 'a' * 5
'aaaaa'

Es decir,

>>> 'a'.__mul__(5)
'aaaaa'

El hecho de que la operación sea diferente requiere un tipo de escritura fuerte; sin embargo, lo contrario de *coaccionar los valores a números antes de multiplicar aún no necesariamente haría que los valores se tipeen débilmente.


Java

El ejemplo de Java String result = "1" + 1;funciona solo porque, por conveniencia, el operador +está sobrecargado de cadenas. El +operador Java reemplaza la secuencia con la creación de StringBuilder(ver esto ):

String result = a + b;
// becomes something like
String result = new StringBuilder().append(a).append(b).toString()

Este es más bien un ejemplo de tipeo muy estático, sin ninguna coerción real: StringBuildertiene un método append(Object)que se usa específicamente aquí. La documentación dice lo siguiente:

Agrega la representación de cadena del Objectargumento.

El efecto general es exactamente como si el argumento convirtiera el argumento en una cadena String.valueOf(Object)y los caracteres de esa cadena se agregaran a esta secuencia de caracteres.

Donde String.valueOfentonces

Devuelve la representación de cadena del argumento Object. [Devuelve] si el argumento es null, entonces una cadena igual a "null"; de lo contrario, obj.toString()se devuelve el valor de .

Por lo tanto, este es un caso de absolutamente ninguna coerción por parte del lenguaje, delegando toda preocupación a los objetos en sí.


C#

De acuerdo con la respuesta de Jon Skeet aquí , el operador +ni siquiera está sobrecargado para la stringclase, similar a Java, esto es solo la conveniencia generada por el compilador, gracias a la escritura tanto estática como fuerte.


Perl

Como explica el perldata ,

Perl tiene tres tipos de datos integrados: escalares, matrices de escalares y matrices asociativas de escalares, conocidas como "hashes". Un escalar es una cadena única (de cualquier tamaño, limitada solo por la memoria disponible), un número o una referencia a algo (que se analizará en perlref). Las matrices normales son listas ordenadas de escalares indexados por número, comenzando con 0. Los hashes son colecciones desordenadas de valores escalares indexados por su clave de cadena asociada.

Sin embargo, Perl no tiene un tipo de datos separado para números, booleanos, cadenas, nulos, undefineds, referencias a otros objetos, etc., solo tiene un tipo para todos estos, el tipo escalar; 0 es un valor escalar tanto como "0". Una variable escalar que se estableció como una cadena realmente puede cambiar a un número y, a partir de ahí, comportarse de manera diferente a "solo una cadena", si se accede a ella en un contexto numérico. El escalar puede contener cualquier cosa en Perl, es tanto el objeto como existe en el sistema. mientras que en Python los nombres solo se refieren a los objetos, en Perl los valores escalares en los nombres son objetos modificables. Además, el sistema de tipo orientado a objetos está pegado encima de esto: solo hay 3 tipos de datos en perl: escalares, listas y hashes. Un objeto definido por el usuario en Perl es una referencia (es decir, un puntero a cualquiera de los 3 anteriores) blesseditado a un paquete: puede tomar dicho valor y bendecirlo a cualquier clase en el instante que desee.

Perl incluso le permite cambiar las clases de valores a su antojo; esto no es posible en Python, donde crear un valor de alguna clase necesita construir explícitamente el valor que pertenece a esa clase con object.__new__o similar. En Python realmente no puedes cambiar la esencia del objeto después de la creación, en Perl puedes hacer casi cualquier cosa:

package Foo;
package Bar;

my $val = 42;
# $val is now a scalar value set from double
bless \$val, Foo;
# all references to $val now belong to class Foo
my $obj = \$val;
# now $obj refers to the SV stored in $val
# thus this prints: Foo=SCALAR(0x1c7d8c8)
print \$val, "\n"; 
# all references to $val now belong to class Bar
bless \$val, Bar;
# thus this prints Bar=SCALAR(0x1c7d8c8)
print \$val, "\n";
# we change the value stored in $val from number to a string
$val = 'abc';
# yet still the SV is blessed: Bar=SCALAR(0x1c7d8c8)
print \$val, "\n";
# and on the course, the $obj now refers to a "Bar" even though
# at the time of copying it did refer to a "Foo".
print $obj, "\n";

por lo tanto, la identidad de tipo está débilmente ligada a la variable y se puede cambiar a través de cualquier referencia sobre la marcha. De hecho, si lo haces

my $another = $val;

\$anotherno tiene la identidad de clase, aunque \$valtodavía dará la referencia bendecida.


TL; DR

Hay mucho más sobre la escritura débil en Perl que solo las coerciones automáticas, y se trata más de que los tipos de los valores en sí mismos no están establecidos en piedra, a diferencia de Python, que es un lenguaje de escritura dinámica pero muy fuerte. Eso da pitón TypeErroren 1 + "1"una indicación de que el lenguaje es fuertemente tipado, aunque el uno en contra de hacer algo útil, como en Java o C # no se opone a ellos siendo idiomas inflexible.

Antti Haapala
fuente
Esto es totalmente confuso. Que las variables de Perl 5 no tengan tipos no influye en los valores , que siempre tienen un tipo.
Jim Balter
@JimBalter bueno, sí, un valor tiene un tipo que es una cadena o un número, y puede comportarse de manera diferente en algún contexto dependiendo de si la variable escalar contenía una cadena o un número; pero el valor contenido en una variable puede cambiar de tipo simplemente accediendo a la variable, y dado que el valor en sí mismo vive en la variable, los valores en sí mismos pueden considerarse mutables entre los tipos.
Antti Haapala
Los valores no cambian tipos, eso es incoherente; un valor siempre es de un tipo . El valor que contiene una variable puede cambiar. Un cambio de 1 a "1" es tanto un cambio de valor como un cambio de 1 a 2.
Jim Balter
Un lenguaje de tipo débil como Perl permite que el primer tipo de cambio de valor ocurra implícitamente dependiendo del contexto. Pero incluso C ++ permite tales conversiones implícitas a través de definiciones de operador. La escritura débil es una propiedad muy informal y realmente no es una forma útil de describir idiomas, como señaló Eric Lippert.
Jim Balter
PD Se puede demostrar que, incluso en Perl, <digits> y "<digits>" tienen valores diferentes, no solo tipos diferentes. Perl hace que <digits> y "<digits>" parezcan tener el mismo valor en la mayoría de los casos mediante conversiones implícitas , pero la ilusión no está completa; por ejemplo, "12" | "34" es 36 mientras que 12 | 34 es 46. Otro ejemplo es que "00" es numéricamente igual a 00 en la mayoría de los contextos, pero no en el contexto booleano, donde "00" es verdadero pero 00 es falso.
Jim Balter
1

Como muchos otros han expresado, la noción completa de mecanografía "fuerte" versus "débil" es problemática.

Como arquetipo, Smalltalk está muy fuertemente tipado: siempre generará una excepción si una operación entre dos objetos es incompatible. Sin embargo, sospecho que pocos en esta lista llamarían a Smalltalk un lenguaje fuertemente tipado, porque se escribe dinámicamente.

Encuentro la noción de mecanografía "estática" versus "dinámica" más útil que "fuerte" versus "débil". Un lenguaje de tipo estático tiene todos los tipos resueltos en tiempo de compilación, y el programador tiene que declarar explícitamente si no.

Contraste con un lenguaje de escritura dinámica, donde la escritura se realiza en tiempo de ejecución. Por lo general, esto es un requisito para los lenguajes polimórficos, de modo que las decisiones sobre si una operación entre dos objetos es legal no tiene que ser decidida previamente por el programador.

En lenguajes polimórficos, de tipo dinámico (como Smalltalk y Ruby), es más útil pensar en un "tipo" como una "conformidad con el protocolo". Si un objeto obedece un protocolo de la misma manera que otro objeto, incluso si los dos objetos no comparten ninguna herencia o mixins u otro vudú, el sistema de tiempo de ejecución los considera el mismo "tipo". Más correctamente, un objeto en tales sistemas es autónomo y puede decidir si tiene sentido responder a un mensaje en particular que se refiera a un argumento en particular.

¿Quiere un objeto que pueda dar una respuesta significativa al mensaje "+" con un argumento de objeto que describa el color azul? Puede hacerlo en lenguajes de tipo dinámico, pero es una molestia en los idiomas de tipo estático.

Jan Steinman
fuente
3
Creo que el concepto de tipeo dinámico vs estático no está en discusión. Aunque tengo que decir que no creo que el polimorfismo esté de alguna manera perjudicado en lenguajes de tipo estático. Finalmente, el sistema de tipos verifica si una operación dada es aplicable a los operandos dados, ya sea en tiempo de ejecución o en tiempo de compilación. Además, otras formas de polimorfismo, como las funciones y clases paramétricas, permiten combinar tipos en lenguajes de tipo estático de formas que usted describió como muy difíciles en comparación con las de tipo dinámico, incluso mejores si se proporciona la inferencia de tipos.
Edwin Dalorzo
0

Me gusta la respuesta de @Eric Lippert , pero para abordar la pregunta, los lenguajes fuertemente tipados generalmente tienen un conocimiento explícito de los tipos de variables en cada punto del programa. Los idiomas con tipos débiles no lo hacen, por lo que pueden intentar realizar una operación que puede no ser posible para un tipo en particular. Creo que la forma más fácil de ver esto es en una función. C ++:

void func(string a) {...}

Se asabe que la variable es de tipo cadena y cualquier operación incompatible se detectará en tiempo de compilación.

Pitón:

def func(a)
  ...

La variable apodría ser cualquier cosa y podemos tener un código que llame a un método no válido, que solo quedará atrapado en tiempo de ejecución.

Lubo Antonov
fuente
12
Creo que puede confundir la escritura dinámica frente a la escritura estática con la escritura fuerte frente a la escritura débil. En ambas versiones de su código, los sistemas de tipo de tiempo de ejecución saben muy bien que a es una cadena. Es solo que en el primer caso, el compilador puede decirte que, en el segundo, no puede. Pero esto no hace que ninguno de estos idiomas tenga un tipo débil.
Edwin Dalorzo