¿Por qué no puede haber conversiones implícitas?

14

Según tengo entendido, las conversiones implícitas pueden causar errores.

Pero eso no tiene sentido: ¿no deberían las conversiones normales también causar errores, entonces?

¿Por qué no tener

len(100)

trabajar por el lenguaje interpretándolo (o compilándolo) como

len(str(100))

especialmente porque esa es la única forma (que sé) de que funcione. El idioma sabe cuál es el error, ¿por qué no solucionarlo?

Para este ejemplo usé Python, aunque siento que para algo tan pequeño es básicamente universal.

Quelklef
fuente
2
perl -e 'print length(100);'impresiones 3.
2
Y así, la naturaleza del lenguaje y su sistema de tipos. Eso es parte del diseño de Python.
2
No lo arregla, porque no conoce tu itnesion. quizás quisiste hacer algo completamente diferente. como demandar a un bucle pero nunca antes había hecho algo como programar. así que si lo arregla por sí mismo, el usuario no sabe que estaba equivocado ni que la implementación no hará lo que espera.
Zaibis
55
@PieCrust ¿Cómo se supone que Python se convertirá? Es no cierto que todas las conversiones posibles devolvería el mismo resultado.
Bakuriu
77
@PieCrust: las cadenas y las matrices son iterables. ¿Por qué sería strla forma implícita de convertir un int en un iterable? ¿qué tal range? o bin( hex, oct) o chr(o unichr)? Todos esos retornos iterables, incluso si strparece lo más obvio para usted en esta situación.
njzk2

Respuestas:

37

Por lo que vale len(str(100)), len(chr(100))y len(hex(100))son todos diferentes. nostr es la única forma de hacerlo funcionar, ya que hay más de una conversión diferente en Python de un entero a una cadena. Uno de ellos, por supuesto, es el más común, pero no necesariamente pasa sin decir que es a lo que te refieres. Una conversión implícita literalmente significa "no hace falta decir".

Uno de los problemas prácticos con las conversiones implícitas es que no siempre es obvio qué conversión se aplicará cuando hay varias posibilidades, y esto hace que los lectores cometan errores al interpretar el código porque no logran descubrir la conversión implícita correcta. Todos siempre dicen que lo que pretendían es la "interpretación obvia". Es obvio para ellos porque es lo que querían decir. Puede que no sea obvio para alguien más.

Es por eso que (la mayoría de las veces) Python prefiere explícito a implícito, prefiere no arriesgarse. El caso principal en el que Python sí hace la coerción de tipo es en aritmética. Permite 1 + 1.0porque la alternativa sería demasiado molesto para vivir, pero que no permite 1 + "1"ya que piensa que debería tener que especificar si quieres decir int("1"), float("1"), ord("1"), str(1) + "1", o alguna otra cosa. Tampoco permite (1,2,3) + [4,5,6], aunque podría definir reglas para elegir un tipo de resultado, al igual que define reglas para elegir el tipo de resultado 1 + 1.0.

Otros idiomas no están de acuerdo y tienen muchas conversiones implícitas. Cuanto más incluyen, menos obvios se vuelven. ¡Intente memorizar las reglas del estándar C para "promociones enteras" y "conversiones aritméticas habituales" antes del desayuno!

Steve Jessop
fuente
+1 Para demostrar cómo la suposición inicial, que lensolo puede funcionar con un tipo, es fundamentalmente errónea.
KChaloux
2
@KChaloux: en realidad, en mi ejemplo str, ¡ chry hextodos devuelven el mismo tipo! Estaba tratando de pensar en un tipo diferente cuyo constructor puede tomar solo un int, pero aún no se me ocurre nada. Lo ideal sería un contenedor Xdonde len(X(100)) == 100;-) numpy.zerosparece un poco oscuro.
Steve Jessop
2
Para ver ejemplos de los posibles problemas, consulte: docs.google.com/document/d/… y destroyallsoftware.com/talks/wat
Jens Schauder
1
La conversión implícita (creo que se llama coacción) puede causar cosas desagradables como tener a == b, b == cpero a == cno es cierto.
bgusach
Excelente respuesta ¡Todo lo que quería decir, solo estaba mejor! Mi única objeción menor es que las promociones de enteros en C son bastante simples en comparación con la memorización de las reglas de coerción de JavaScript, o envolver la cabeza en una base de código C ++ con el uso descuidado de constructores implícitos.
GrandOpener
28

Según tengo entendido, las conversiones implícitas pueden causar errores.

Te falta una palabra: las conversiones implícitas pueden causar errores de tiempo de ejecución .

Para un caso simple como el que muestra, está bastante claro lo que quiso decir. Pero los idiomas no pueden funcionar en los casos. Necesitan trabajar con reglas. Para muchas otras situaciones, no está claro si el programador cometió un error (usando el tipo incorrecto) o si el programador pretendía hacer lo que el código supone que pretendía hacer.

Si el código se supone incorrecto, obtendrá un error de tiempo de ejecución. Debido a que son tediosos de rastrear, muchos idiomas se equivocan al decirle que cometió un error y le permiten decirle a la computadora lo que realmente quiso decir (corregir el error o hacer la conversión explícitamente). Otros lenguajes adivinan, ya que su estilo se presta a un código rápido y fácil que es más fácil de depurar.

Una cosa a tener en cuenta es que las conversiones implícitas hacen que el compilador sea un poco más complejo. Debe tener más cuidado con los ciclos (intentemos esta conversión de A a B; ¡Uy, eso no funcionó, pero hay una conversión de B a A! Y luego también debe preocuparse por los ciclos de tamaño C y D ), que Es una pequeña motivación para evitar conversiones implícitas.

Telastyn
fuente
Principalmente hablaba de funciones que solo pueden funcionar con un tipo, lo que hace obvia la conversión. ¿Cuál sería uno que funcionaría con varios tipos, y el tipo que pones marca la diferencia? print ("295") = print (295), por lo que eso no haría una diferencia, excepto con variables. Lo que dijiste tiene sentido, excepto por el párrafo 3d ... ¿Puedes reiterar?
Quelklef
30
@PieCrust - 123 + "456", ¿quería "123456" o 579? Los lenguajes de programación no hacen contexto, por lo que es difícil para ellos "resolverlo", ya que tendrían que conocer el contexto en el que se está haciendo la adición. ¿Qué párrafo no está claro?
Telastyn
1
Además, tal vez la interpretación como base-10 no es lo que quería. Sí, las conversiones implícitas son algo bueno , pero solo cuando no pueden ocultar un error.
Deduplicador
13
@PieCrust: Para ser sincero, nunca esperaría len(100)volver 3. Me resultaría mucho más intuitivo calcular el número de bits (o bytes) en la representación de 100.
user541686
3
Estoy con @Mehrdad, de su ejemplo, está claro que se piensa que es 100% obvio que len(100)debe darle la cantidad de caracteres de la representación decimal del número 100. Pero eso es en realidad un montón de suposiciones que está haciendo, mientras que desconocen de ellos. Podría presentar argumentos sólidos por los que 64debería devolver el número de bits o bytes o caracteres de la representación hexadecimal ( -> 2 caracteres) len().
funkwurm
14

Las conversiones implícitas son bastante posibles de hacer. La situación en la que te metes en problemas es cuando no sabes de qué manera algo debería funcionar.

Un ejemplo de esto se puede ver en Javascript, donde el +operador trabaja de diferentes maneras en diferentes momentos.

>>> 4 + 3
7
>>> "4" + 3
43
>>> 4 + "3"
43

Si uno de los argumentos es una cadena, entonces el +operador es una concatenación de cadenas, de lo contrario es una suma.

Si se le da un argumento y no sabe si es una cadena o un número entero, y desea agregarlo, puede ser un poco complicado.

Otra forma de lidiar con esto es desde la herencia básica (de la cual se desprende Perl; ver Programación es difícil, vamos a crear secuencias de comandos ... )

En Basic, la lenfunción solo tiene sentido al ser invocada en una Cadena (documentos para visual basic : "Cualquier expresión de Cadena válida o nombre de variable. Si la Expresión es de tipo Objeto, la función Len devuelve el tamaño tal como será escrito en el archivo por la función FilePut ").

Perl sigue este concepto de contexto. La confusión que existe en JavaScript con la conversión implícita de tipos para el +operador es a veces suma y otras concatenación no ocurre en perl porque siempre+ es suma y siempre es concatenación..

Si se usa algo en un contexto escalar, es un escalar (por ejemplo, al usar una lista como escalar, la lista se comporta como si fuera un número correspondiente a su longitud). Si usa un operador de cadena ( eqpara prueba de igualdad, cmppara comparación de cadena), el escalar se usa como si fuera una cadena. Del mismo modo, si algo se usó en un contexto matemático ( ==para prueba de igualdad y <=>para comparación numérica), el escalar se usa como si fuera un número.

La regla fundamental para toda programación es "hacer lo que menos sorprende a la persona". Esto no significa que no haya sorpresas allí, pero el esfuerzo es sorprender a la persona lo menos.

Yendo a un primo cercano de perl - php, hay situaciones en las que un operador puede actuar sobre algo en contextos de cadena o numéricos y el comportamiento puede ser sorprendente para las personas. El ++operador es uno de esos ejemplos. En números, se comporta exactamente como se esperaba. Al actuar sobre una cadena, como "aa", incrementa la cadena ( $foo = "aa"; $foo++; echo $foo;imprime ab). También se volcará para que azcuando se incremente se haga ba. Esto no es particularmente sorprendente todavía.

$foo = "3d8";
echo "$foo\n";
$foo++;
echo "$foo\n";
$foo++;
echo "$foo\n";
$foo++;
echo "$foo\n";

( ideona )

Esto imprime:

3d8
3d9
3e0
4

Bienvenido a los peligros de las conversiones implícitas y los operadores que actúan de manera diferente en la misma cadena. (Perl maneja ese bloque de código de manera un poco diferente; decide que "3d8"cuando ++se aplica el operador es un valor numérico desde el principio y va de 4inmediato ( ideone ), este comportamiento se describe bien en perlop: incremento automático y decremento automático )

Ahora, por qué un idioma hace algo de una manera y otro lo hace de otra manera llega a los pensamientos de diseño de los diseñadores. La filosofía de Perl es que hay más de una forma de hacerlo , y puedo pensar en varias formas de realizar algunas de estas operaciones. Por otro lado, Python tiene una filosofía descrita en PEP 20 - El Zen de Python que establece (entre otras cosas): "Debe haber una, y preferiblemente solo una, forma obvia de hacerlo".

Estas diferencias de diseño han llevado a diferentes idiomas. Hay una forma de obtener la longitud de un número en Python. La conversión implícita va en contra de esta filosofía.

Lectura relacionada: ¿Por qué Ruby no tiene una conversión implícita de Fixnum en String?

Comunidad
fuente
En mi humilde opinión, un buen lenguaje / marco a menudo debe tener múltiples formas de hacer las cosas que manejan los casos de esquina comunes de manera diferente (mejor manejar un caso de esquina común una vez en un lenguaje o marco de trabajo que 1000 veces en 1000 programas); El hecho de que dos operadores hagan lo mismo la mayor parte del tiempo no debe considerarse algo malo, si cuando sus comportamientos difieren, habrá variaciones en cada variación. No debería ser difícil comparar dos variables numéricas xy yde tal manera que produzca una relación de equivalencia o clasificación, pero los operadores de comparación del IIRC Python ...
supercat
... no implementan relaciones de equivalencia ni clasificación, y Python no proporciona ningún operador conveniente que lo haga.
supercat
12

ColdFusion hizo la mayor parte de esto. Define un conjunto de reglas para manejar sus conversiones implícitas, tiene un único tipo de variable y listo.

El resultado es la anarquía total, donde agregar "4a" a 6 es 6.16667.

¿Por qué? Bueno, debido a que la primera de las dos variables es un número, entonces el resultado será numérico. "4a" se analiza como una fecha y se ve como "4 AM". 4 AM es 4: 00/24: 00, o 1/6 de día (0.16667). Agregue a 6 y obtendrá 6.16667.

Las listas tienen caracteres separadores predeterminados de una coma, por lo que si alguna vez agrega un elemento a una lista que contiene una coma, simplemente agrega dos elementos. Además, las listas son cadenas secretas, por lo que se pueden analizar como fechas si solo contienen 1 elemento.

La comparación de cadenas verifica si ambas cadenas se pueden analizar primero en fechas. Porque no hay tipo de fecha, lo hace. Lo mismo para los números y cadenas que contienen números, notación octal y booleanos ("verdadero" y "sí", etc.)

En lugar de fallar rápidamente e informar un error, en su lugar, ColdFusion manejará las conversiones de tipo de datos por usted. Y no en el buen sentido.

Por supuesto, puede corregir las conversiones implícitas llamando a funciones explícitas como DateCompare ... pero luego ha perdido los "beneficios" de la conversión implícita.


Para ColdFusion, esta es una forma de ayudar a capacitar a los desarrolladores. Especialmente cuando todos esos desarrolladores están acostumbrados a HTML (ColdFusion funciona con etiquetas, se llama CFML, luego también agregaron secuencias de comandos <cfscript>, donde no es necesario agregar etiquetas para todo). Y funciona bien cuando solo quieres que algo funcione. Pero cuando necesita que las cosas sucedan de manera más precisa, necesita un lenguaje que se niegue a hacer conversiones implícitas para cualquier cosa que parezca que podría haber sido un error.

Pimgd
fuente
1
wow, "anarquía total" de hecho!
hoosierEE
7

Estás diciendo que las conversiones implícitas podrían ser una buena idea para operaciones que no son ambiguas, como int a = 100; len(a)cuando obviamente quieres convertir el int en una cadena antes de llamar.

Pero está olvidando que estas llamadas pueden ser sintácticamente inequívocas, pero pueden representar un error tipográfico realizado por el programador que pretendía pasar a1, que es una cadena. Este es un ejemplo artificial, pero con IDEs que proporcionan autocompletado para nombres de variables, estos errores ocurren.

El sistema de tipos tiene como objetivo ayudarnos a evitar errores, y para los idiomas que eligen una verificación de tipo más estricta, las conversiones implícitas socavan eso.

Eche un vistazo a los problemas de Javascript con todas las conversiones implícitas realizadas por ==, tanto que ahora muchos recomiendan seguir con el operador de conversión no implícita ===.

Avner Shahar-Kashtan
fuente
6

Considere por un momento el contexto de su declaración. Dices que esta es la "única forma" en que podría funcionar, pero ¿estás realmente seguro de que funcionará así? ¿Qué hay de esto?

def approx_log_10(s):
    return len(s)
print approx_log_10(3.5)  # "3" is probably not what I'm expecting here...

Como otros han mencionado, en su ejemplo muy específico, parece simple para el compilador intuir lo que quiso decir. Pero en un contexto más amplio de programas más complicados, a menudo no es del todo obvio si usted quiso ingresar una cadena, o tipeó un nombre de variable para otro, o llamó a la función incorrecta, o cualquiera de las docenas de otros posibles errores lógicos .

En el caso en que el intérprete / compilador adivine lo que quiso decir, a veces funciona mágicamente, y otras veces pasa horas depurando algo que misteriosamente no funciona sin razón aparente. Es mucho más seguro, en el caso promedio, que le digan que la afirmación no tiene sentido, y que pase unos segundos corrigiéndola según lo que realmente quiso decir.

GrandOpener
fuente
3

Las conversiones implícitas pueden ser un verdadero dolor para trabajar. En PowerShell:

$a = $(dir *.xml) # typeof a is a list, because there are two XML files in the folder.
$a = $(dir *.xml) # typeof a is a string, because there is one XML file in the folder.

De repente, se necesitan el doble de pruebas y el doble de errores que habría sin una conversión implícita.

Esben Skov Pedersen
fuente
2

Los lanzamientos explícitos son importantes porque aclaran su intención. En primer lugar, el uso de modelos explícitos cuenta una historia a alguien que está leyendo su código. Revelan que intencionalmente hiciste lo que hiciste. Además, lo mismo se aplica al compilador. Lo siguiente es ilegal en C # por ejemplo

double d = 3.1415926;
// code elided
int i = d;

El elenco te hará perder precisión, lo que fácilmente podría ser un error. Por lo tanto, el compilador se niega a compilar. Al usar un reparto explícito, le está diciendo al compilador: "Oye, sé lo que estoy haciendo". Y él irá: "¡Está bien!" Y compilar.

Paul Kertscher
fuente
1
En realidad, no me gustan más los moldes explícitos que los implícitos; En mi humilde opinión, lo único que (X)ydebe significar es "Creo que Y es convertible a X; hacer la conversión si es posible, o lanzar una excepción si no"; Si una conversión tiene éxito, uno debería poder predecir cuál será el valor resultante * sin tener que conocer ninguna regla especial sobre la conversión de tipo ya tipo X. Si se les solicita convertir -1.5 y 1.5 a enteros, algunos idiomas producirán (-2,1); algunos (-2,2), algunos (-1,1) y algunos (-1,2). Si bien las reglas de C son apenas oscuras ...
supercat
1
... ese tipo de conversión parece que se realizaría de manera más apropiada a través del método que a través de la conversión (lástima .NET no incluye funciones eficientes de redondear y convertir, y los Java están sobrecargados de una manera algo tonta).
supercat
Le estás diciendo al compilador " Creo que sé lo que estoy haciendo".
gnasher729
@ gnasher729 Hay algo de verdad en eso :)
Paul Kertscher
1

Los idiomas que admiten conversiones / conversiones implícitas, o tipeo débil, como a veces se le conoce, harán suposiciones para usted que no siempre coinciden con el comportamiento que pretendía o esperaba. Los diseñadores del lenguaje pueden haber tenido el mismo proceso de pensamiento que usted para un cierto tipo de conversión o serie de conversiones, y en ese caso nunca verá un problema; sin embargo, si no lo hicieron, entonces su programa fallará.

Sin embargo, la falla puede o no ser obvia. Dado que estas conversiones implícitas se producen en tiempo de ejecución, su programa se ejecutará felizmente y solo verá un problema cuando observe el resultado o el resultado de su programa. Un lenguaje que requiere conversiones explícitas (algunos se refieren a esto como tipeo fuerte) le daría un error incluso antes de que su programa comience la ejecución, lo cual es un problema mucho más obvio y más fácil de solucionar que las conversiones implícitas salieron mal.

El otro día, un amigo mío le preguntó a su hijo de 2 años qué era 20 + 20 y él respondió 2020. Le dije a mi amigo: "Se convertirá en un programador de Javascript".

En Javascript:

20+20
40

20+"20"
"2020"

Por lo tanto, puede ver los problemas que pueden crear los moldes implícitos y por qué no es algo que solo se pueda solucionar. La solución, diría, es usar modelos explícitos.

Fred Thomsen
fuente