Por que não pode haver conversões implícitas?

14

Pelo que entendi, conversões implícitas podem causar erros.

Mas isso não faz sentido - as conversões normais também não devem causar erros?

Por que não

len(100)

trabalho pelo idioma interpretando (ou compilando) como

len(str(100))

especialmente porque essa é a única maneira (eu sei) de funcionar. A linguagem sabe qual é o erro, por que não corrigi-lo?

Para este exemplo, usei Python, embora sinta que, para algo tão pequeno, seja basicamente universal.

Quelklef
fonte
2
perl -e 'print length(100);'imprime 3.
2
E assim a natureza da linguagem e seu sistema de tipos. Isso faz parte do design do python.
2
Não conserta, porque não conhece sua itnesion. talvez você queira fazer algo completamente diferente. como processar um loop, mas nunca fez nada como programar antes. portanto, se o problema for corrigido por si próprio, o usuário não saberá que ele estava errado nem que a implementação não fará o que ele espera.
Zaibis 26/05
5
@PieCrust Como o Python deve converter? É não verdade que toda a conversão possível seria devolver o mesmo resultado.
Bakuriu
7
@PieCrust: strings e matrizes são iteráveis. por que strseria a maneira implícita de converter um int em um iterável? que tal range? ou bin( hex, oct) ou chr(ou unichr)? Todos esses retornam iteráveis, mesmo que strpareça o mais óbvio para você nessa situação.
Njzk2 26/05

Respostas:

37

Pelo que vale len(str(100)), len(chr(100))e len(hex(100))são todos diferentes. nãostr é a única maneira de fazê-lo funcionar, pois há mais de uma conversão diferente no Python de um número inteiro para uma string. Um deles, é claro, é o mais comum, mas não é necessariamente desnecessário dizer que foi o que você quis dizer. Uma conversão implícita significa literalmente "escusado será dizer".

Um dos problemas práticos das conversões implícitas é que nem sempre é óbvio qual conversão será aplicada onde houver várias possibilidades, e isso resulta em erros nos leitores ao interpretar o código porque eles não conseguem descobrir a conversão implícita correta. Todo mundo sempre diz que o que eles pretendiam é a "interpretação óbvia". É óbvio para eles, porque é o que eles querem dizer. Pode não ser óbvio para outra pessoa.

É por isso que (na maioria das vezes) o Python prefere explícito ao implícito, prefere não arriscar. O principal caso em que o Python faz a coerção de tipos é na aritmética. Ele permite 1 + 1.0porque a alternativa seria muito chato viver com, mas ele não permite 1 + "1", porque ele pensa que você deve ter para especificar se você quer dizer int("1"), float("1"), ord("1"), str(1) + "1", ou qualquer outra coisa. Também não permite (1,2,3) + [4,5,6], mesmo que possa definir regras para escolher um tipo de resultado, assim como define regras para escolher o tipo de resultado 1 + 1.0.

Outros idiomas discordam e têm muitas conversões implícitas. Quanto mais eles incluem, menos óbvios eles se tornam. Tente memorizar as regras do padrão C para "promoções com números inteiros" e "conversões aritméticas comuns" antes do café da manhã!

Steve Jessop
fonte
+1 Para demonstrar como a suposição inicial, que lensó pode funcionar com um tipo, é fundamentalmente falha.
KChaloux
2
@KChaloux: na verdade, no meu exemplo str, chre hextodos retornam o mesmo tipo! Eu estava tentando pensar em um tipo diferente cujo construtor pode ter apenas um int, mas ainda não criei nada. Ideal seria um container Xonde len(X(100)) == 100;-) numpy.zerosparece um pouco obscuro.
Steve Jessop
2
Para exemplos dos possíveis problemas, consulte: docs.google.com/document/d/… e destroyallsoftware.com/talks/wat
Jens Schauder
1
Conversão implícita (acho que isso é chamado de coerção) pode causar coisas desagradáveis como ter a == b, b == cmas a == cnão ser verdade.
bgusach
Excelente resposta. Tudo o que eu queria dizer era apenas melhor! Minha única queixa menor é que as promoções com números inteiros em C são bastante simples em comparação com a memorização das regras de coerção do JavaScript ou a compreensão de uma base de código C ++ com o uso descuidado de construtores implícitos.
GrandOpener
28

Pelo que entendi, conversões implícitas podem causar erros.

Está faltando uma palavra: conversões implícitas podem causar erros de tempo de execução .

Para um caso simples como você mostra, é bem claro o que você quis dizer. Mas os idiomas não podem funcionar em casos. Eles precisam trabalhar com regras. Para muitas outras situações, não está claro se o programador cometeu um erro (usando o tipo errado) ou se o programador pretendia fazer o que o código supõe que ele pretendia fazer.

Se o código assumir errado, você receberá um erro de tempo de execução. Como eles são tediosos para rastrear, muitos idiomas erram ao dizer que você errou e deixou que você dissesse ao computador o que realmente queria dizer (corrija o erro ou faça explicitamente a conversão). Outras linguagens adivinham, já que seu estilo se presta a códigos rápidos e fáceis que são mais fáceis de depurar.

Uma coisa a observar é que as conversões implícitas tornam o compilador um pouco mais complexo. Você precisa ter mais cuidado com os ciclos (vamos tentar esta conversão de A para B; oops que não funcionou, mas há uma conversão de B para A! E, em seguida, você também precisa se preocupar com os ciclos de tamanho C e D ), que é uma pequena motivação para evitar conversões implícitas.

Telastyn
fonte
Principalmente estava falando de funções que só podem funcionar com um tipo, tornando a conversão óbvia. O que seria aquele que funcionaria com vários tipos e o tipo que você coloca faz a diferença? print ("295") = print (295), para que não faça diferença, exceto pelas variáveis. O que você disse faz sentido, exceto o parágrafo 3d ... Você pode reiterar?
Quelklef 26/05
30
@PieCrust - 123 + "456", você queria "123456" ou 579? As linguagens de programação não fazem contexto, por isso é difícil para elas "descobrir", pois precisariam conhecer o contexto em que a adição está sendo feita. Qual parágrafo não está claro?
Telastyn 26/05
1
Além disso, talvez a interpretação como base 10 não seja o que você queria. Sim, conversões implícitas são boas , mas somente quando elas não podem ocultar um erro.
Deduplicator
13
@PieCrust: Para ser sincero, nunca esperaria len(100)voltar 3. Eu acharia muito mais intuitivo calcular o número de bits (ou bytes) na representação de 100.
User541686
3
Estou com o @Mehrdad, pelo seu exemplo, é claro que você acha 100% óbvio que len(100)deve fornecer a quantidade de caracteres da representação decimal do número 100. Mas isso é realmente uma tonelada de suposições que você está fazendo sem saber deles. Você pode argumentar fortemente por que o número de bits ou bytes ou caracteres da representação hexadecimal ( 64-> 2 caracteres) deve ser retornado por len().
Funkwurm
14

É possível fazer conversões implícitas. A situação em que você se mete em problemas é quando você não sabe para que lado algo deve funcionar.

Um exemplo disso pode ser visto em Javascript, onde o +operador trabalha de maneiras diferentes em momentos diferentes.

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

Se um dos argumentos for uma sequência, o +operador é uma concatenação, caso contrário, é adição.

Se você recebe um argumento e não sabe se é uma sequência ou um número inteiro, e deseja fazer uma adição a ela, pode ser um pouco confuso.

Outra maneira de lidar com isso é a herança básica (que o perl segue - veja Programação é difícil, vamos usar scripts ... )

No Basic, a lenfunção faz sentido apenas ser chamada em uma String (documentos para visual basic : "Qualquer expressão String válida ou nome de variável. Se Expression for do tipo Object, a função Len retornará o tamanho conforme será gravado no arquivo por a função FilePut. ").

Perl segue esse conceito de contexto. A confusão que existe em JavaScript com a conversão implícita de tipos para o +operador sendo, por vezes, adição e, por vezes, a concatenação não acontece em perl, porque +é sempre além e .é sempre concatenação.

Se algo é usado em um contexto escalar, é um escalar (por exemplo, usando uma lista como escalar, a lista se comporta como se fosse um número correspondente ao seu comprimento). Se você usar um operador de string ( eqpara teste de igualdade, cmppara comparação de string), o escalar será usado como se fosse uma string. Da mesma forma, se algo foi usado em um contexto matemático ( ==para teste de igualdade e <=>para comparação numérica), o escalar é usado como se fosse um número.

A regra fundamental para toda a programação é "faça o que menos surpreende a pessoa". Isso não significa que não há surpresas, mas o esforço é surpreender a pessoa o mínimo.

Indo para um primo próximo do perl - php, há situações em que um operador pode atuar em algo em contextos de string ou numéricos e o comportamento pode surpreender as pessoas. O ++operador é um exemplo. Nos números, ele se comporta exatamente como o esperado. Ao atuar em uma sequência, como "aa", ela a incrementa ( $foo = "aa"; $foo++; echo $foo;imprime ab). Também rolará para que, azquando incrementado, se torne ba. Isso não é particularmente surpreendente ainda.

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

( ideona )

Isso imprime:

3d8
3d9
3e0
4

Bem-vindo aos perigos de conversões implícitas e operadores agindo de maneira diferente na mesma sequência. (O Perl lida com esse bloco de código de maneira um pouco diferente - ele decide que "3d8"quando o ++operador é aplicado é um valor numérico desde o início e passa 4imediatamente ( ideone ) - esse comportamento é bem descrito no perlop: Incremento automático e decremento automático )

Agora, por que uma linguagem faz algo de uma maneira e outra faz de outra maneira chega aos pensamentos de design dos designers. A filosofia de Perl é que há mais de uma maneira de fazer isso - e posso pensar em várias maneiras de realizar algumas dessas operações. Por outro lado, o Python tem uma filosofia descrita no PEP 20 - O Zen do Python, que afirma (entre outras coisas): "Deveria haver uma - e de preferência apenas uma - maneira óbvia de fazê-lo".

Essas diferenças de design levaram a diferentes idiomas. Existe uma maneira de obter o comprimento de um número no Python. A conversão implícita vai contra essa filosofia.

Leitura relacionada: Por que Ruby não possui uma conversão implícita de Fixnum em String?

Comunidade
fonte
IMHO, uma boa linguagem / estrutura frequentemente deve ter várias maneiras de fazer coisas que lidam com casos de canto comuns de maneira diferente (melhor lidar com um caso de canto comum uma vez em um idioma ou estrutura do que 1000 vezes em 1000 programas); o fato de dois operadores fazerem a mesma coisa na maioria das vezes não deve ser considerado ruim, se quando seus comportamentos diferirem, haverá benefícios para cada variação. Não deve ser difícil comparar duas variáveis numéricas xe yde tal forma um como para produzir uma relação ou a classificação de equivalência, mas operadores de comparação de IIRC Python ...
Supercat
... implementa nem relações de equivalência nem classificação, e o Python não fornece nenhum operador conveniente que o faça.
Supercat
12

O ColdFusion fez a maior parte disso. Ele define um conjunto de regras para lidar com suas conversões implícitas, tem um único tipo de variável e pronto.

O resultado é anarquia total, onde adicionar "4a" a 6 é 6.16667.

Por quê? Bem, como a primeira das duas variáveis ​​é um número, o resultado será numérico. "4a" é analisado como uma data e visto como "04:00". 04:00 é 04:00 / 24:00, ou 1/6 de um dia (0,16666). Adicione a 6 e você obtém 6.16667.

As listas possuem caracteres separadores padrão de uma vírgula; portanto, se você adicionar um item a uma lista que contenha uma vírgula, basta adicionar dois itens. Além disso, as listas são sequências secretas, para que possam ser analisadas como datas se contiverem apenas 1 item.

A comparação de cadeias verifica se ambas as cadeias podem ser analisadas primeiro para datas. Porque não há tipo de data, faz isso. O mesmo vale para números e cadeias contendo números, notação octal e booleanos ("true" e "yes" etc.)

Em vez de falhar rapidamente e relatar um erro, o ColdFusion manipulará as conversões de tipo de dados para você. E não de um jeito bom.

Obviamente, você pode corrigir as conversões implícitas chamando funções explícitas como DateCompare ... mas depois perde os "benefícios" da conversão implícita.


Para o ColdFusion, essa é uma maneira de ajudar a capacitar os desenvolvedores. Especialmente quando todos esses desenvolvedores estão acostumados a HTML (o ColdFusion trabalha com tags, é chamado CFML, depois eles também adicionam scripts <cfscript>, onde você não precisa adicionar tags para tudo). E funciona bem quando você deseja que algo funcione. Mas quando você precisa que as coisas aconteçam de uma maneira mais precisa, você precisa de um idioma que se recuse a fazer conversões implícitas para qualquer coisa que pareça ter sido um erro.

Pimgd
fonte
1
uau, "anarquia total" de fato!
hoosierEE
7

Você está dizendo que conversões implícitas podem ser uma boa idéia para operações que não são ambíguas, como int a = 100; len(a), onde você obviamente deseja converter o int em uma string antes de chamar.

Mas você está esquecendo que essas chamadas podem ser sintaticamente inequívocas, mas podem representar um erro de digitação feito pelo programador que pretendia passar a1, que é uma string. Este é um exemplo artificial, mas com os IDEs fornecendo preenchimento automático para nomes de variáveis, esses erros acontecem.

O sistema de tipos visa nos ajudar a evitar erros e, para idiomas que escolhem a verificação de tipo mais rigorosa, as conversões implícitas prejudicam isso.

Confira os problemas do Javascript com todas as conversões implícitas realizadas por ==, tanto que agora muitos recomendam manter o operador no-implicit-conversion ===.

Avner Shahar-Kashtan
fonte
6

Considere por um momento o contexto de sua declaração. Você diz que esse é o "único jeito" de funcionar, mas você tem certeza de que funcionará assim? Que tal isso:

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

Como outros já mencionaram, em seu exemplo muito específico, parece simples para o compilador intuir o que você quis dizer. Porém, em um contexto maior de programas mais complicados, muitas vezes não é óbvio se você deseja inserir uma string ou digitar um nome de variável para outra ou chamar a função errada ou qualquer uma das dezenas de outros erros lógicos em potencial. .

No caso em que o intérprete / compilador adivinha o que você quis dizer, às vezes funciona magicamente e, outras vezes, você passa horas depurando algo que misteriosamente não funciona sem motivo aparente. É muito mais seguro, no caso médio, saber que a afirmação não faz sentido e gastar alguns segundos corrigindo-a do que você realmente quis dizer.

GrandOpener
fonte
3

Conversões implícitas podem ser uma verdadeira dor de se trabalhar. No 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, há o dobro de testes necessários e o dobro de erros que haveria sem conversão implícita.

Esben Skov Pedersen
fonte
2

Elencos explícitos são importantes, pois eles estão deixando sua intenção clara. Antes de tudo, usar conversões explícitas conta uma história para alguém que está lendo seu código. Eles revelam que você intencionalmente fez o que fez. Além disso, o mesmo se aplica ao compilador. O seguinte é ilegal em C #, por exemplo

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

O elenco fará com que você perca a precisão, o que pode ser facilmente um bug. Portanto, o compilador se recusa a compilar. Ao usar um elenco explícito, você está dizendo ao compilador: "Ei, eu sei o que estou fazendo." E ele irá: "Ok!" E compilar.

Paul Kertscher
fonte
1
Na verdade, eu não gosto mais da maioria dos elencos explícitos do que dos implícitos; IMHO, a única coisa que (X)ydeveria significar é "eu acho que Y é conversível em X; faça a conversão, se possível, ou lance uma exceção, se não"; se uma conversão for bem-sucedida, é possível prever qual será o valor resultante * sem a necessidade de conhecer regras especiais sobre a conversão de um tipo ypara outro X. Se solicitado a converter -1,5 e 1,5 em números inteiros, alguns idiomas renderiam (-2,1); alguns (-2,2), alguns (-1,1) e outros (-1,2). Embora as regras do C dificilmente são obscurecer ...
supercat
1
... esse tipo de conversão parece que seria realizado de maneira mais apropriada por método do que por cast (muito ruim. NET não inclui funções eficientes de conversão e conversão, e as Java são sobrecarregadas de maneira um tanto boba).
Supercat
Você está dizendo ao compilador " Acho que sei o que estou fazendo".
gnasher729 22/01
@ gnasher729 Há alguma verdade nisso :)
Paul Kertscher
1

Os idiomas que suportam conversões / conversões implícitas, ou digitação fraca, como às vezes é referido, farão suposições para você que nem sempre correspondem ao comportamento que você pretendia ou esperava. Os designers do idioma podem ter tido o mesmo processo de pensamento que você teve para um determinado tipo de conversão ou série de conversões e, nesse caso, você nunca verá um problema; no entanto, se não o fizeram, seu programa falhará.

A falha, no entanto, pode ou não ser óbvia. Como essas transmissões implícitas ocorrem em tempo de execução, seu programa será executado felizmente e você verá um problema apenas quando examinar a saída ou o resultado do programa. Uma linguagem que exija conversões explícitas (algumas se referem a isso como digitação forte) causaria um erro antes que o programa iniciasse a execução, o que é um problema muito mais óbvio e mais fácil de corrigir que as conversões implícitas deram errado.

Outro dia, um amigo meu perguntou a ele com 2 anos de idade o que era 20 + 20 e ele respondeu em 2020. Eu disse ao meu amigo: "Ele vai se tornar um programador Javascript".

Em Javascript:

20+20
40

20+"20"
"2020"

Assim, você pode ver os problemas que os lançamentos implícitos podem criar e por que não é algo que pode ser corrigido. A correção que eu argumentaria é usar moldes explícitos.

Fred Thomsen
fonte