Warum kann es keine impliziten Konvertierungen geben?


14

Nach meinem Verständnis können implizite Konvertierungen Fehler verursachen.

Das macht aber keinen Sinn - sollten normale Konvertierungen dann nicht auch Fehler verursachen?

Warum nicht haben

len(100)

arbeiten nach der Sprache, die sie interpretiert (oder kompiliert) als

len(str(100))

Zumal das der einzige Weg ist , damit es funktioniert. Die Sprache weiß, was der Fehler ist. Warum nicht beheben?

Für dieses Beispiel habe ich Python verwendet, obwohl ich der Meinung bin, dass es für etwas so Kleines grundsätzlich universell ist.


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

2
Und damit die Natur der Sprache und ihres Typensystems. Das gehört zum Design von Python.

2
Es behebt das Problem nicht, weil es Ihre Situation nicht kennt. Vielleicht wolltest du etwas komplett anderes machen. Ich mag es, eine Schleife zu verklagen, habe aber noch nie etwas Ähnliches programmiert. Wenn es also von selbst behoben wird, weiß der Benutzer weder, dass er sich geirrt hat, noch, dass die Implemantation nicht das tut, was er erwartet.
Zaibis

5
@ PieCrust Wie soll Python konvertieren? Es ist nicht wahr, dass alle möglichen Konvertierungen dasselbe Ergebnis liefern würden.
Bakuriu

7
@ PieCrust: Strings und Arrays sind Iterables. Warum ist strdies der implizite Weg, ein int in ein iterables zu konvertieren? wie wäre es range? oder bin( hex, oct) oder chr(oder unichr)? Alle diese Elemente geben iterables zurück, auch wenn strdies für Sie in dieser Situation am offensichtlichsten erscheint.
njzk2

Antworten:


37

Für das, was es wert ist len(str(100)), len(chr(100))und len(hex(100))sind alle unterschiedlich. strDies ist nicht die einzige Möglichkeit, dies zum Laufen zu bringen, da es in Python mehr als eine unterschiedliche Konvertierung von einer Ganzzahl in eine Zeichenfolge gibt. Eine davon ist natürlich die häufigste, aber es muss nicht selbstverständlich sein, dass Sie diese gemeint haben. Eine implizite Konvertierung bedeutet wörtlich "es versteht sich von selbst".

Eines der praktischen Probleme bei impliziten Konvertierungen ist, dass es nicht immer offensichtlich ist, welche Konvertierung angewendet wird, wenn verschiedene Möglichkeiten bestehen. Dies führt dazu, dass Leser Fehler bei der Interpretation des Codes machen, weil sie die korrekte implizite Konvertierung nicht herausfinden. Jeder sagt immer, dass das, was er beabsichtigt hat, die "offensichtliche Interpretation" ist. Es ist für sie offensichtlich, weil es das ist, was sie meinten. Für andere ist es möglicherweise nicht offensichtlich.

Aus diesem Grund zieht Python (die meiste Zeit) das explizite dem impliziten vor, es zieht es vor, es nicht zu riskieren. Der Hauptfall, in dem Python Typenzwang ausführt, liegt in der Arithmetik. Es erlaubt , 1 + 1.0weil die Alternative zu lästig sein würde , damit zu leben, aber es nicht zulässt , dass , 1 + "1"weil es denkt , sollten Sie festlegen , ob Sie meinen int("1"), float("1"), ord("1"), str(1) + "1"oder etwas anderes. Es erlaubt auch nicht (1,2,3) + [4,5,6], obwohl es Regeln zur Auswahl eines Ergebnistyps definieren könnte , genau wie es Regeln zur Auswahl des Ergebnistyps definiert 1 + 1.0.

Andere Sprachen stimmen nicht überein und haben viele implizite Konvertierungen. Je mehr sie enthalten, desto weniger offensichtlich werden sie. Versuchen Sie, sich vor dem Frühstück die Regeln aus dem C-Standard für "ganzzahlige Aktionen" und "übliche arithmetische Umrechnungen" zu merken!


+1 Um zu demonstrieren, wie die Startannahme, die lennur mit einem Typ funktionieren kann, grundlegend fehlerhaft ist.
KChaloux

2
@KChaloux: tatsächlich, in meinem Beispiel str, chrund hextun alle Rückkehr die gleiche Art! Ich habe versucht, mir einen anderen Typ vorzustellen, dessen Konstruktor nur ein int aufnehmen kann, aber mir ist noch nichts eingefallen. Ideal wäre ein Container, Xin dem len(X(100)) == 100;-) numpy.zerosetwas dunkel wirkt.
Steve Jessop

2
Beispiele für mögliche Probleme finden Sie hier: docs.google.com/document/d/… und destroyallsoftware.com/talks/wat
Jens Schauder

1
Implizite Konvertierung (Ich denke , das Zwang genannt wird) kann böse Dinge verursacht wie mit a == b, b == caber a == cnicht wahr zu sein.
bgusach

Hervorragende Antwort. Alles was ich sagen wollte, nur besser formuliert! Mein einziger kleiner Kritikpunkt ist, dass C-Integer-Promotions im Vergleich zum Speichern von JavaScript-Zwangsregeln oder zum Umschließen einer C ++ - Codebasis mit unachtsamer Verwendung impliziter Konstruktoren recht einfach sind.
GrandOpener

28

Nach meinem Verständnis können implizite Konvertierungen Fehler verursachen.

Sie vermissen ein Wort: Implizite Konvertierungen können zu Laufzeitfehlern führen.

Für einen einfachen Fall wie Sie ist es ziemlich klar, was Sie meinten. Aber Sprachen können nicht an Fällen arbeiten. Sie müssen mit Regeln arbeiten. In vielen anderen Situationen ist nicht klar, ob der Programmierer einen Fehler gemacht hat (mit dem falschen Typ) oder ob der Programmierer beabsichtigt hat, das zu tun, was der Code voraussetzt.

Wenn der Code falsch ist, erhalten Sie einen Laufzeitfehler. Da es mühsam ist, sie aufzuspüren, gehen viele Sprachen daneben, Ihnen mitzuteilen, dass Sie Fehler gemacht haben, und Sie können dem Computer mitteilen, was Sie wirklich gemeint haben (beheben Sie den Fehler oder führen Sie die Konvertierung explizit durch). Andere Sprachen raten, da sich ihr Stil für schnellen und einfachen Code eignet, der leichter zu debuggen ist.

Zu beachten ist, dass implizite Konvertierungen den Compiler etwas komplexer machen. Sie müssen vorsichtiger mit Zyklen umgehen (probieren wir diese Konvertierung von A nach B aus; Hoppla, das hat nicht funktioniert, aber es gibt eine Konvertierung von B nach A! Und dann müssen Sie sich auch um Zyklen der Größe C und D kümmern ) ist eine kleine Motivation, implizite Conversions zu vermeiden.


Meistens ging es um Funktionen, die nur mit einem Typ arbeiten können, wodurch die Konvertierung offensichtlich wurde. Was würde mit mehreren Typen funktionieren und der Typ, den Sie eingeben, macht einen Unterschied? print ("295") = print (295), damit es keinen Unterschied macht, außer bei Variablen. Was Sie sagten, macht Sinn, außer für den 3D-Absatz ... Können Sie es wiederholen?
Quelklef

30
@ PieCrust - 123 + "456", wollten Sie "123456" oder 579? Programmiersprachen machen keinen Kontext, daher ist es für sie schwierig, "herauszufinden", da sie den Kontext kennen müssten, in dem die Hinzufügung erfolgt. Welcher Absatz ist unklar?
Telastyn

1
Vielleicht ist die Interpretation als Basis 10 auch nicht das, was Sie wollten. Ja, implizite Konvertierungen sind eine gute Sache , aber nur dort, wo sie einen Fehler unmöglich verbergen können.
Deduplikator

13
@ PieCrust: Um ehrlich zu sein, würde ich niemals damit rechnen len(100), zurückzukehren 3. Ich würde es viel intuitiver finden, die Anzahl der Bits (oder Bytes) in der Darstellung von zu berechnen 100.
user541686

3
Ich bin bei @Mehrdad. Aus Ihrem Beispiel geht hervor, dass Sie der Meinung sind , dass es zu 100% offensichtlich ist, len(100)dass Sie die Anzahl der Zeichen für die Dezimaldarstellung der Zahl 100 erhalten sollten von ihnen. Sie könnten starke Argumente dafür vorbringen, warum die Anzahl der Bits oder Bytes oder Zeichen der hexadezimalen Darstellung ( 64-> 2 Zeichen) von zurückgegeben werden soll len().
funkwurm

14

Implizite Konvertierungen sind durchaus möglich. Die Situation, in der Sie in Schwierigkeiten geraten, ist, wenn Sie nicht wissen, wie etwas funktionieren soll.

Ein Beispiel hierfür ist Javascript, bei dem der +Bediener zu verschiedenen Zeiten auf unterschiedliche Weise arbeitet.

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

Wenn eines der Argumente eine Zeichenfolge ist, ist der +Operator eine Zeichenfolgenverkettung, andernfalls handelt es sich um eine Addition.

Wenn Sie ein Argument erhalten und nicht wissen, ob es sich um eine Zeichenfolge oder eine Ganzzahl handelt, und dies ergänzen möchten, kann dies zu einem Durcheinander führen.

Ein anderer Weg, um damit umzugehen, ist das Basic-Erbe (das Perl folgt aus - siehe Programmierung ist schwierig, Let's Go Scripting ... ).

In Basic lenmacht die Funktion nur Sinn, wenn sie für einen String aufgerufen wird (docs for visual basic : "Jeder gültige String-Ausdruck oder Variablenname. Wenn der Ausdruck vom Typ Object ist, gibt die Len-Funktion die Größe zurück, von der aus er in die Datei geschrieben wird die FilePut-Funktion. ").

Perl folgt diesem Kontextkonzept. Die Verwirrung , die mit impliziter Umwandlung von Typen für den in JavaScript vorhanden +Operator ist manchmal zusätzlich und manchmal in Perl - Verkettung geschieht nicht , weil +ist immer zusätzlich und .ist immer Verkettung.

Wenn etwas in einem skalaren Kontext verwendet wird, ist es ein Skalar (z. B. bei Verwendung einer Liste als Skalar verhält sich die Liste so, als wäre es eine Zahl, die ihrer Länge entspricht). Wenn Sie einen String-Operator verwenden ( eqfür den Gleichheitstest, cmpfür den String-Vergleich), wird der Skalar so verwendet, als wäre er ein String. Wenn etwas in einem mathematischen Kontext verwendet wurde ( ==für die Gleichheitsprüfung und <=>für den numerischen Vergleich), wird der Skalar genauso verwendet, als ob es eine Zahl wäre.

Die Grundregel für jede Programmierung lautet: "Tu das, was die Person am wenigsten überrascht". Das heißt nicht, dass es dort keine Überraschungen gibt, aber die Anstrengung besteht darin, die Person am wenigsten zu überraschen.

Es gibt Situationen, in denen ein Operator in einem String oder in einem numerischen Kontext auf etwas einwirken kann und das Verhalten für Menschen überraschend sein kann. Der ++Operator ist ein solches Beispiel. Bei Zahlen verhält es sich genau wie erwartet. Wenn Sie auf eine Zeichenfolge einwirken, z. B. "aa", wird die Zeichenfolge inkrementiert ( $foo = "aa"; $foo++; echo $foo;gedruckt ab). Es wird auch überrollen, so dass, azwenn inkrementiert wird ba. Das ist noch nicht besonders überraschend.

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

( ideone )

Dies druckt aus:

3d8
3d9
3e0
4

Willkommen zu den Gefahren impliziter Konvertierungen und zu Operatoren, die auf derselben Zeichenfolge unterschiedlich handeln. (Perl behandelt diesen Codeblock ein bisschen anders - es entscheidet, dass "3d8"der ++Operator von Anfang an ein numerischer Wert ist und geht 4sofort zu ( ideone ) - dieses Verhalten ist in perlop gut beschrieben : Auto-Inkrement und Auto-Dekrement. )

Warum eine Sprache etwas auf die eine und eine andere Weise tut, führt zu den Designgedanken der Designer. Perls Philosophie ist, dass es mehr als einen Weg gibt, dies zu tun - und ich kann mir eine Reihe von Wegen vorstellen, um einige dieser Operationen durchzuführen. Auf der anderen Seite hat Python eine in PEP 20 - The Zen of Python - beschriebene Philosophie, die (unter anderem) lautet: "Es sollte einen - und vorzugsweise nur einen - offensichtlichen Weg geben, dies zu tun."

Diese Designunterschiede haben zu unterschiedlichen Sprachen geführt. Es gibt eine Möglichkeit, die Länge einer Zahl in Python zu ermitteln. Implizite Konvertierung widerspricht dieser Philosophie.

Verwandte Lektüre: Warum hat Ruby keine implizite Konvertierung von Fixnum in String?


Meiner Meinung nach sollte eine gute Sprache / ein gutes Framework oft mehrere Möglichkeiten haben, Dinge zu tun, die gemeinsame Eckfälle unterschiedlich behandeln (es ist besser, einen gemeinsamen Eckfall einmal in einer Sprache oder einem Framework als 1000-mal in 1000 Programmen zu behandeln). Die Tatsache, dass zwei Bediener die meiste Zeit dasselbe tun, sollte nicht als schlecht angesehen werden, wenn sich ihre Verhaltensweisen unterscheiden und jede Variation Vorteile bringt. Es sollte nicht schwierig sein , zwei numerischen Variablen zu vergleichen xund yin einer solchen Art und Weise , wie eine Äquivalenzbeziehung zu erhalten oder das Ranking, aber IIRC Pythons Vergleichsoperatoren ...
supercat

... implementieren weder Äquivalenzrelationen noch Rangfolgen, und Python bietet keine passenden Operatoren, die dies tun.
Supercat

12

ColdFusion hat das meiste getan. Es definiert eine Reihe von Regeln, um Ihre impliziten Konvertierungen zu handhaben, hat einen einzelnen Variablentyp und los geht's.

Das Ergebnis ist eine totale Anarchie, bei der die Addition von "4a" zu 6 6.16667 ergibt.

Warum? Nun, da die erste der beiden Variablen eine Zahl ist, ist das Ergebnis numerisch. "4a" wird als Datum analysiert und als "4 Uhr morgens" angesehen. 4 Uhr morgens ist 4: 00/24: 00 oder 1/6 eines Tages (0,16667). Addiere zu 6 und du erhältst 6.16667.

Listen haben Standardtrennzeichen eines Kommas. Wenn Sie also jemals ein Element zu einer Liste hinzufügen, die ein Komma enthält, haben Sie gerade zwei Elemente hinzugefügt. Listen sind auch geheime Zeichenfolgen, sodass sie als Datumsangaben analysiert werden können, wenn sie nur 1 Element enthalten.

Der Zeichenfolgenvergleich überprüft, ob beide Zeichenfolgen zuerst zu Datumsangaben analysiert werden können. Da es keinen Datumstyp gibt, wird dieser verwendet. Gleiches gilt für Zahlen und Zeichenfolgen mit Zahlen, Oktalnotation und Booleschen Werten ("true" und "yes" usw.).

Anstatt schnell zu versagen und einen Fehler zu melden, übernimmt ColdFusion die Datentypkonvertierung für Sie. Und nicht in guter Weise.

Natürlich können Sie die impliziten Konvertierungen korrigieren, indem Sie explizite Funktionen wie DateCompare aufrufen ... aber dann haben Sie die "Vorteile" der impliziten Konvertierung verloren.


Für ColdFusion ist dies eine Möglichkeit, Entwickler zu stärken. Besonders wenn all diese Entwickler an HTML gewöhnt sind (ColdFusion arbeitet mit Tags, es heißt CFML, später fügten sie auch Skripte über hinzu <cfscript>, wobei Sie nicht für alles Tags hinzufügen müssen). Und es funktioniert gut, wenn Sie nur etwas zum Laufen bringen möchten. Wenn Sie jedoch eine präzisere Vorgehensweise benötigen, benötigen Sie eine Sprache, die implizite Konvertierungen für alles ablehnt, was so aussieht, als wäre es ein Fehler gewesen.


1
Wow, "totale Anarchie" in der Tat!
hoosierEE

7

Sie sagen, dass implizite Konvertierungen eine gute Idee für Operationen sein könnten, die eindeutig sind, z. B. int a = 100; len(a)wenn Sie vor dem Aufruf das int offensichtlich in einen String konvertieren möchten.

Aber Sie vergessen , dass diese Anrufe können syntaktisch eindeutig, aber sie können einen Tippfehler gemacht durch den Programmierer dar , die passieren gemeint a1, das ist eine Zeichenfolge. Dies ist ein erfundenes Beispiel, aber mit IDEs, die die automatische Vervollständigung für Variablennamen bereitstellen, treten diese Fehler auf.

Das Typensystem soll uns helfen, Fehler zu vermeiden, und für Sprachen, die eine strengere Typprüfung wählen, untergraben implizite Konvertierungen dies.

Sehen Sie sich die Probleme von Javascript mit all den impliziten Konvertierungen an, die von == durchgeführt werden, so dass viele jetzt empfehlen, sich an den no-implicit-conversion-Operator === zu halten.


6

Betrachten Sie für einen Moment den Kontext Ihrer Aussage. Sie sagen, dies ist der "einzige Weg", wie es funktionieren könnte, aber sind Sie wirklich sicher, dass es so funktionieren wird? Was ist damit:

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

Wie andere bereits erwähnt haben, scheint es für den Compiler in Ihrem Beispiel einfach zu sein, zu verstehen, was Sie gemeint haben. In einem größeren Zusammenhang mit komplizierteren Programmen ist es jedoch oft gar nicht offensichtlich, ob Sie einen String eingeben oder einen Variablennamen für einen anderen tippen oder die falsche Funktion oder einen von Dutzenden anderen möglichen Logikfehlern aufrufen wollten .

In dem Fall, in dem der Interpreter / Compiler errät, was Sie gemeint haben, funktioniert dies manchmal auf magische Weise, und manchmal verbringt man Stunden damit, etwas zu debuggen, was auf mysteriöse Weise ohne ersichtlichen Grund nicht funktioniert. Im Durchschnitt ist es viel sicherer zu wissen, dass die Aussage keinen Sinn ergibt, und einige Sekunden damit zu verbringen, sie zu korrigieren, was Sie eigentlich gemeint haben.


3

Implizite Konvertierungen können ein echtes Problem sein. 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.

Plötzlich sind doppelt so viele Tests und Fehler erforderlich wie ohne implizite Konvertierung.


2

Explizite Darstellungen sind wichtig, um Ihre Absichten klar zu machen. Zuallererst erzählt die Verwendung expliziter Besetzungen jemandem eine Geschichte, der Ihren Code liest. Sie zeigen, dass Sie absichtlich das getan haben, was Sie getan haben. Im Übrigen gilt das Gleiche für den Compiler. Das Folgende ist in C # beispielsweise unzulässig

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

Durch die Besetzung verlieren Sie an Präzision, was leicht ein Fehler sein kann. Daher weigert sich der Compiler zu kompilieren. Indem Sie eine explizite Besetzung verwenden, sagen Sie dem Compiler: "Hey, ich weiß, was ich tue." Und er wird gehen: "Okay!" Und kompilieren.


1
Eigentlich mag ich die meisten expliziten Casts nicht so sehr wie implizite; Meiner (X)yMeinung nach sollte das Einzige heißen: "Ich denke, Y ist konvertierbar zu X; mach die Konvertierung, wenn möglich, oder wirf eine Ausnahme, wenn nicht." Wenn eine Konvertierung erfolgreich ist, sollte man in der Lage sein, den resultierenden Wert * vorherzusagen, ohne spezielle Regeln für die Konvertierung vom Typ yzum Typ kennen zu müssen X. Wenn Sie aufgefordert werden, -1,5 und 1,5 in Ganzzahlen umzuwandeln, ergeben einige Sprachen (-2,1). einige (-2,2), einige (-1,1) und einige (-1,2). Während C Regeln sind kaum dunkel ...
Supercat

1
... diese Art der Konvertierung scheint angemessener per Methode als per Cast durchzuführen zu sein (zu schade, dass .NET keine effizienten Round-and-Convert-Funktionen enthält und Java etwas albern überladen ist).
Supercat

Sie sagen dem Compiler: " Ich glaube, ich weiß, was ich tue."
gnasher729

@ gnasher729 Da ist etwas Wahres dran :)
Paul Kertscher

1

Sprachen, die implizite Konvertierungen / Umwandlungen oder schwache Typisierung unterstützen, wie sie manchmal genannt wird, werden Annahmen für Sie treffen, die nicht immer mit dem beabsichtigten oder erwarteten Verhalten übereinstimmen. Die Designer der Sprache hatten möglicherweise den gleichen Denkprozess wie Sie für eine bestimmte Art von Konvertierung oder Serie von Konvertierungen, und in diesem Fall werden Sie nie ein Problem sehen. Wenn dies jedoch nicht der Fall ist, schlägt Ihr Programm fehl.

Der Fehler kann jedoch offensichtlich sein oder auch nicht. Da diese impliziten Umwandlungen zur Laufzeit auftreten, wird Ihr Programm problemlos ausgeführt, und Sie werden nur dann ein Problem feststellen, wenn Sie sich die Ausgabe oder das Ergebnis Ihres Programms ansehen. Eine Sprache, die explizite Umwandlungen erfordert (einige bezeichnen dies als starke Typisierung), würde Ihnen einen Fehler melden, bevor Sie mit der Ausführung des Programms beginnen. Dies ist ein viel offensichtlicheres und einfacher zu behebendes Problem, wenn implizite Umwandlungen fehlgeschlagen sind.

Neulich fragte ein Freund seinen 2-Jährigen, was 20 + 20 sei, und er antwortete 2020. Ich sagte meinem Freund: "Er wird ein Javascript-Programmierer."

In Javascript:

20+20
40

20+"20"
"2020"

So können Sie sehen, welche Probleme implizite Casts verursachen können und warum dies nicht einfach behoben werden kann. Der Fix, den ich argumentieren würde, ist die Verwendung expliziter Casts.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.