Perché non ci possono essere conversioni implicite?


14

A quanto ho capito, le conversioni implicite possono causare errori.

Ma questo non ha senso: le normali conversioni non dovrebbero anche causare errori?

Perché non averlo

len(100)

opera interpretando (o compilando) la lingua come

len(str(100))

soprattutto perché è l' unico modo (lo so) per farlo funzionare. La lingua sa qual è l'errore, perché non risolverlo?

Per questo esempio ho usato Python, anche se ritengo che per qualcosa di così piccolo sia sostanzialmente universale.


2
perl -e 'print length(100);'stampe 3.

2
E quindi la natura della lingua e il suo sistema di tipi. Questo fa parte del design di Python.

2
Non lo risolve, perché non conosce la tua itnesion. forse volevi fare qualcosa di completamente diverso. come fare causa ad un loop ma non ho mai fatto nulla di simile alla programmazione prima. quindi se lo risolve da solo l'utente non sa né di aver torto né che l'implementazione non farà ciò che si aspetta.
Zaibis,

5
@PieCrust Come dovrebbe essere convertito Python? E ' non è vero che tutto possibile conversione sarebbe tornato lo stesso risultato.
Bakuriu,

7
@PieCrust: stringhe e array sono iterabili. perché sarebbe stril modo implicito di convertire un int in un iterabile? che ne dici range? o bin( hex, oct) o chr(o unichr)? Tutti questi restituiscono iterabili, anche se strsembra il più ovvio per te in questa situazione.
njzk2,

Risposte:


37

Per quello che vale len(str(100)), len(chr(100))e len(hex(100))sono tutti diversi. nonstr è l'unico modo per farlo funzionare, dal momento che c'è più di una diversa conversione in Python da un numero intero a una stringa. Uno di questi ovviamente è il più comune, ma non necessariamente va detto che è quello che intendevi. Una conversione implicita significa letteralmente "è ovvio".

Uno dei problemi pratici con le conversioni implicite è che non è sempre ovvio quale conversione verrà applicata laddove ci sono diverse possibilità, e questo fa sì che i lettori commettano errori nell'interpretazione del codice perché non riescono a capire la corretta conversione implicita. Tutti dicono sempre che ciò che intendevano è "ovvia interpretazione". È ovvio per loro perché è quello che volevano dire. Potrebbe non essere ovvio per qualcun altro.

Questo è il motivo per cui (la maggior parte delle volte) Python preferisce esplicito a implicito, preferisce non rischiare. Il caso principale in cui Python digita la coercizione è in aritmetica. Permette 1 + 1.0perché l'alternativa sarebbe troppo fastidioso per vivere, ma non permette 1 + "1", perché pensa che si dovrebbe avere per specificare se si intende int("1"), float("1"), ord("1"), str(1) + "1", o qualcos'altro. Inoltre non consente (1,2,3) + [4,5,6], anche se potrebbe definire le regole per scegliere un tipo di risultato, proprio come definisce le regole per scegliere il tipo di risultato 1 + 1.0.

Altre lingue non sono d'accordo e hanno molte conversioni implicite. Più includono, meno diventano evidenti. Prova a memorizzare le regole dello standard C per le "promozioni intere" e le "solite conversioni aritmetiche" prima di colazione!


+1 Per dimostrare come l'assunto iniziale, che lenpuò funzionare solo con un tipo, sia fondamentalmente imperfetto.
KChaloux,

2
@KChaloux: in realtà, nel mio esempio str, chre hextutti restituiscono lo stesso tipo! Stavo cercando di pensare a un tipo diverso il cui costruttore può prendere solo un int, ma non ho ancora trovato nulla. L'ideale sarebbe un contenitore in Xcui len(X(100)) == 100;-) numpy.zerossembra un po 'oscuro.
Steve Jessop,

2
Per esempi di possibili problemi di vedere questo: docs.google.com/document/d/... e destroyallsoftware.com/talks/wat
Jens Schauder

1
La conversione implicita (credo che questo si chiama la coercizione) può causare brutte cose come avere a == b, b == cma a == cnon conformi.
bgusach,

Risposta eccellente. Tutto quello che volevo dire, solo una migliore espressione! Il mio unico piccolo cavillo è che le promozioni di numeri interi C sono piuttosto semplici rispetto alla memorizzazione delle regole di coercizione JavaScript o al fatto di avvolgere la testa attorno a una base di codice C ++ con un uso incauto dei costruttori impliciti.
GrandOpener,

28

A quanto ho capito, le conversioni implicite possono causare errori.

Ti manca una parola: le conversioni implicite possono causare errori di runtime .

Per un caso semplice come quello che mostri, è abbastanza chiaro cosa volevi dire. Ma le lingue non possono funzionare sui casi. Devono lavorare con le regole. Per molte altre situazioni, non è chiaro se il programmatore abbia commesso un errore (usando il tipo sbagliato) o se il programmatore intendesse fare ciò che il codice presume intendesse fare.

Se il codice presuppone un errore, viene visualizzato un errore di runtime. Dal momento che sono noiosi da rintracciare, molte lingue errano a dirti che hai fatto un casino e che ti permettono di dire al computer cosa intendevi veramente (correggere il bug o fare esplicitamente la conversione). Altre lingue fanno ipotesi, poiché il loro stile si presta a un codice semplice e veloce che è più facile da eseguire il debug.

Una cosa da notare è che le conversioni implicite rendono il compilatore un po 'più complesso. Devi stare più attento ai cicli (proviamo questa conversione da A a B; oops che non ha funzionato, ma c'è una conversione da B a A! E quindi devi preoccuparti anche dei cicli di dimensioni C e D ), che è una piccola motivazione per evitare conversioni implicite.


Principalmente parlava di funzioni che possono funzionare solo con un tipo, rendendo evidente la conversione. Quale sarebbe quello che funzionerebbe con più tipi e il tipo che inserisci fa la differenza? print ("295") = print (295), quindi non farebbe differenza, tranne che con le variabili. Quello che hai detto ha senso, tranne per il paragrafo 3d ... Puoi ribadirlo?
Quelklef,

30
@PieCrust - 123 + "456", volevi "123456" o 579? I linguaggi di programmazione non fanno contesto, quindi è difficile per loro "capirlo", dal momento che dovrebbero conoscere il contesto in cui viene fatta l'aggiunta. In quale paragrafo non è chiaro?
Telastyn,

1
Inoltre, forse l'interpretazione come base-10 non è ciò che volevi. Sì, le conversioni implicite sono una buona cosa , ma solo dove non possono nascondere un errore.
Deduplicatore,

13
@PieCrust: A dire il vero non mi aspetterei mai len(100)di tornare 3. Lo troverei molto più intuitivo per calcolare il numero di bit (o byte) nella rappresentazione di 100.
user541686,

3
Sono con @Mehrdad, dal vostro esempio è chiaro che si pensa che è evidente al 100% che len(100)dovrebbe dare il numero di caratteri della rappresentazione decimale del numero 100. Ma questo è in realtà un sacco di ipotesi che stai facendo mentre ignaro di loro. Si potrebbero fare forti argomentazioni per cui il numero di bit o byte o caratteri della rappresentazione esadecimale ( 64-> 2 caratteri) dovrebbe essere restituito da len().
funkwurm,

14

Le conversioni implicite sono del tutto possibili. La situazione in cui ti trovi nei guai è quando non sai in che modo qualcosa dovrebbe funzionare.

Un esempio di questo può essere visto in Javascript in cui l' +operatore lavora in modi diversi in momenti diversi.

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

Se uno degli argomenti è una stringa, allora il + operatore è una concatenazione di stringhe, altrimenti è un'aggiunta.

Se ti viene dato un argomento e non sai se si tratta di una stringa o di un numero intero e desideri aggiungerlo, può essere un po 'un casino.

Un altro modo per affrontare questo è dall'eredità di base (che segue il perl - vedi Programmazione è difficile, Let's Go Scripting ... )

In Basic, la lenfunzione ha senso solo essere invocata su una stringa (documenti per Visual Basic : "Qualsiasi espressione String valida o nome variabile. Se Expression è di tipo Object, la funzione Len restituisce la dimensione in quanto verrà scritta nel file da la funzione FilePut. ").

Perl segue questo concetto di contesto. La confusione che esiste in JavaScript con la conversione implicita di tipi per l' +operatore a volte addizione e talvolta concatenazione non si verifica in perl perché +è sempre addizione ed .è sempre concatenazione.

Se qualcosa viene usato in un contesto scalare, è uno scalare (ad es. Usando un elenco come scalare, l'elenco si comporta come se fosse un numero corrispondente alla sua lunghezza). Se si utilizza un operatore stringa ( eqper test di uguaglianza, cmpper confronto di stringhe) lo scalare viene utilizzato come se fosse una stringa. Allo stesso modo, se qualcosa è stato usato in un contesto matematico ( ==per test di uguaglianza e<=> per confronto numerico), lo scalare è usato come se fosse un numero.

La regola fondamentale per tutta la programmazione è "fai la cosa che sorprende di meno la persona". Questo non significa che non ci siano sorprese lì dentro, ma lo sforzo è quello di sorprendere la persona il meno.

Andando a stretto contatto con perl - php, ci sono situazioni in cui un operatore può agire su qualcosa in contesti di stringa o numerici e il comportamento può essere sorprendente per le persone. L' ++operatore ne è un esempio. Sui numeri, si comporta esattamente come previsto. Quando agisce su una stringa, ad esempio "aa", aumenta la stringa ( $foo = "aa"; $foo++; echo $foo;stampa ab). Passerà anche in modo che azquando incrementato diventa ba. Questo non è ancora particolarmente sorprendente.

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

( ideone )

Questo stampa:

3d8
3d9
3e0
4

Benvenuti nei pericoli delle conversioni implicite e degli operatori che agiscono in modo diverso sulla stessa stringa. (Perl gestisce quel blocco di codice in modo leggermente diverso - decide che "3d8"quando l' ++operatore viene applicato è un valore numerico dall'inizio e va 4subito ( ideone ) - questo comportamento è ben descritto in perlop: Auto-increment e Auto-decrement )

Ora, perché una lingua fa qualcosa in un modo e un altro in un altro modo arriva ai pensieri progettuali dei designer. La filosofia di Perl è che c'è più di un modo per farlo - e posso pensare a una serie di modi per eseguire alcune di queste operazioni. D'altra parte, Python ha una filosofia descritta in PEP 20 - The Zen of Python che afferma (tra le altre cose): "Dovrebbe esserci uno - e preferibilmente solo un - modo obsoleto di farlo."

Queste differenze di progettazione hanno portato a lingue diverse. C'è un modo per ottenere la lunghezza di un numero in Python. La conversione implicita va contro questa filosofia.

Lettura correlata: Perché Ruby non ha una conversione implicita di Fixnum in String?


IMHO, un buon linguaggio / framework dovrebbe spesso avere più modi di fare cose che gestiscono i casi corner comuni in modo diverso (meglio gestire un caso corner comune una volta in una lingua o framework rispetto a 1000 volte in 1000 programmi); il fatto che due operatori facciano la stessa cosa per la maggior parte del tempo non dovrebbe essere considerato una cosa negativa, se quando i loro comportamenti differiscono ci sarebbero benefici per ogni variazione. Non dovrebbe essere difficile confrontare due variabili numeriche xe yin modo tale da produrre una relazione o una classifica di equivalenza, ma gli operatori di confronto di IIRC Python ...
supercat

... non implementano né relazioni di equivalenza né classifica, e Python non fornisce alcun operatore conveniente che lo faccia.
supercat,

12

ColdFusion ha fatto la maggior parte di questo. Definisce un insieme di regole per gestire le conversioni implicite, ha un singolo tipo di variabile e il gioco è fatto.

Il risultato è l'anarchia totale, dove l'aggiunta di "4a" a 6 è 6.16667.

Perché? Bene, perché la prima delle due variabili è un numero, quindi il risultato sarà numerico. "4a" viene analizzato come una data e visto come "4 AM". 4 AM è 4: 00/24: 00, o 1/6 del giorno (0.16667). Aggiungi a 6 e ottieni 6.16667.

Gli elenchi hanno caratteri di separazione predefiniti di una virgola, quindi se aggiungi mai un elemento a un elenco contenente una virgola, hai appena aggiunto due elementi. Inoltre, gli elenchi sono segretamente stringhe, quindi possono essere analizzati come date se contengono solo 1 elemento.

Il confronto delle stringhe verifica se entrambe le stringhe possono essere prima analizzate nelle date. Perché non esiste un tipo di data, lo fa. Stessa cosa per numeri e stringhe contenenti numeri, notazione ottale e booleani ("true" e "yes", ecc.)

Invece di fallire rapidamente e segnalare un errore, ColdFusion gestirà le conversioni di tipi di dati per te. E non in senso positivo.

Certo, potresti correggere le conversioni implicite chiamando funzioni esplicite come DateCompare ... ma poi hai perso i "vantaggi" della conversione implicita.


Per ColdFusion, questo è un modo per aiutare gli sviluppatori. Soprattutto quando tutti quegli sviluppatori sono abituati all'HTML (ColdFusion funziona con i tag, si chiama CFML, in seguito hanno aggiunto anche gli script <cfscript>, dove non è necessario aggiungere tag per tutto). E funziona bene quando vuoi solo far funzionare qualcosa. Ma quando hai bisogno che le cose accadano in un modo più preciso, hai bisogno di un linguaggio che rifiuta di fare conversioni implicite per tutto ciò che sembra che avrebbe potuto essere un errore.


1
wow, "anarchia totale" davvero!
hoosierEE,

7

Stai dicendo che le conversioni implicite potrebbero essere una buona idea per operazioni non ambigue, come int a = 100; len(a), dove ovviamente intendi convertire l'int in una stringa prima di chiamare.

Ma stai dimenticando che queste chiamate possono essere sintatticamente inequivocabili, ma possono rappresentare un refuso fatto dal programmatore che intendeva passare a1, che è una stringa. Questo è un esempio inventato, ma con gli IDE che forniscono il completamento automatico per i nomi delle variabili, si verificano questi errori.

Il sistema dei tipi ha lo scopo di aiutarci a evitare errori e, per le lingue che scelgono un controllo dei tipi più rigoroso, le conversioni implicite lo minano.

Scopri i guai di Javascript con tutte le conversioni implicite eseguite da ==, al punto che molti ora raccomandano di attenersi all'operatore di conversione non implicita ===.


6

Considera per un momento il contesto della tua affermazione. Dici che questo è "l'unico modo" che potrebbe funzionare, ma sei davvero sicuro che funzionerà così? Che dire di questo:

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

Come altri hanno già detto, nel tuo esempio molto specifico, sembra semplice per il compilatore capire cosa intendevi. Ma in un contesto più ampio di programmi più complicati, spesso non è affatto ovvio se intendevi inserire una stringa, o hai digitato un nome di variabile per un altro, o hai chiamato la funzione sbagliata o una qualsiasi dozzina di altri potenziali errori logici .

Nel caso in cui l'interprete / il compilatore indovinasse cosa intendevi, a volte funziona magicamente, e poi altre volte trascorri ore a eseguire il debug di qualcosa che misteriosamente non funziona senza una ragione apparente. È molto più sicuro, nel caso medio, dire che l'affermazione non ha senso e dedicare qualche secondo a correggerla in base a ciò che realmente intendevi.


3

Le conversioni implicite possono essere un vero dolore con cui lavorare. In 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.

All'improvviso ci sono il doppio dei test necessari e il doppio del numero di bug che ci sarebbero senza una conversione implicita.


2

I cast espliciti sono importanti perché stanno chiarendo le tue intenzioni. Innanzitutto l'uso di cast espliciti racconta una storia a qualcuno che sta leggendo il tuo codice. Rivelano che hai fatto intenzionalmente quello che hai fatto. Lo stesso vale per il compilatore. Quanto segue è illegale in C # per esempio

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

Il cast ti farà perdere la precisione, che potrebbe facilmente essere un bug. Quindi il compilatore si rifiuta di compilare. Usando un cast esplicito stai dicendo al compilatore: "Ehi, so cosa sto facendo." E andrà: "Va bene!" E compilare.


1
In realtà, non mi piacciono i cast espliciti più di quelli impliciti; IMHO, l'unica cosa che (X)ydovrebbe significare è "Penso che Y sia convertibile in X; esegui la conversione se possibile, oppure genera un'eccezione in caso contrario"; se una conversione ha esito positivo, si dovrebbe essere in grado di prevedere quale sarà il valore risultante * senza dover conoscere regole speciali sulla conversione dal tipo di yal tipo X. Se si chiedesse di convertire -1,5 e 1,5 in numeri interi, alcune lingue avrebbero prodotto (-2,1); alcuni (-2,2), alcuni (-1,1) e alcuni (-1,2). Mentre le regole di C sono appena oscure ...
supercat

1
... quel tipo di conversione sembra che verrebbe eseguita in modo più appropriato tramite il metodo piuttosto che tramite il cast (peccato che .NET non includa efficienti funzioni di round-and-convert e Java è sovraccarico in modo piuttosto sciocco).
supercat,

Stai dicendo al compilatore " Penso di sapere cosa sto facendo".
gnasher729,

@ gnasher729 C'è un po 'di verità :)
Paul Kertscher il

1

Le lingue che supportano conversioni / cast impliciti, o digitazione debole come viene talvolta chiamata, faranno ipotesi per te che non sempre corrispondono al comportamento che intendevi o ti aspettavi. I progettisti del linguaggio potrebbero aver avuto lo stesso processo di pensiero che hai fatto per un certo tipo di conversione o serie di conversioni, e in quel caso non vedrai mai un problema; tuttavia, in caso contrario, il tuo programma fallirà.

L'errore, tuttavia, può o meno essere ovvio. Poiché questi cast impliciti si verificano in fase di esecuzione, il programma funzionerà felicemente e vedrai un problema solo quando guardi l'output o il risultato del tuo programma. Un linguaggio che richiede cast espliciti (alcuni si riferiscono a questo come digitazione forte) ti darebbe un errore prima ancora che il programma inizi l'esecuzione, il che è un problema molto più ovvio e più semplice per correggere che i cast impliciti sono andati male.

L'altro giorno un mio amico ha chiesto al suo bambino di 2 anni cosa fossero 20 + 20 e lui ha risposto al 2020. Ho detto al mio amico, "Sta per diventare un programmatore Javascript".

In Javascript:

20+20
40

20+"20"
"2020"

In questo modo puoi vedere i problemi che i cast impliciti possono creare e perché non è qualcosa che può essere semplicemente risolto. La soluzione che direi è usare cast espliciti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.