Skolemisierung existentiell typisierter Ausdrücke

9

In Scala löst der folgende Ausdruck einen Typfehler aus:

%Vor%

Wie in SI-9899 und diesem Antwort , das ist korrekt nach der Spezifikation:

  

Ich denke, das funktioniert nach SLS 6.1: "Das Folgende   Skolemisierungsregel wird universell für jeden Ausdruck angewendet: Wenn der   Typ eines Ausdrucks wäre ein existentieller Typ T, dann der Typ von   der Ausdruck wird stattdessen als Skolemisierung von T angenommen. "

Allerdings verstehe ich das nicht vollständig. Wann wird diese Regel angewendet? Gilt es in der ersten Zeile (dh der Typ von pair ist ein anderer Typ als der Typ Annotation) oder in der zweiten Zeile (aber die Regel auf die zweite Zeile als Ganzes anzuwenden würde nicht zu a führen Typfehler)?

Nehmen wir an, dass SLS 6.1 für die erste Zeile gilt. Es soll existentielle Typen skolemisieren. Wir können das in der ersten Zeile zu einem nicht-existentiellen Typ machen, indem wir das existentielle Element in einen Typparameter setzen:

%Vor%

Es funktioniert! (Kein Typfehler.) Das heißt, dass der existentielle Typ "verloren" wurde, als wir pair definierten? Nein:

%Vor%

Dieser Typ prüft! Wenn es der "Fehler" der Zuweisung zu pair gewesen wäre, sollte dies nicht funktionieren. Der Grund, warum es nicht funktioniert, liegt in der zweiten Zeile. Wenn das der Fall ist, was ist der Unterschied zwischen dem wrap und dem pair Beispiel?

Zum Schluss noch ein paar Beispiele:

%Vor%

Beide funktionieren nicht, aber in Analogie zu der Tatsache, dass wrap.x._1(wrap.x._2) typecheck gemacht hat, hätte ich auch gedacht, dass a2(b2) auch tasten könnte.

    
Dominique Unruh 23.08.2016, 21:10
quelle

1 Antwort

4

Ich glaube, ich habe den größten Teil des Prozesses herausgefunden, wie die obigen Ausdrücke eingegeben werden.

Erstens, was bedeutet das:

  

Die folgende Skolemisierungsregel wird universell für jeden angewendet   Ausdruck: Wenn der Typ eines Ausdrucks ein existentieller Typ wäre   T, dann wird angenommen, dass der Typ des Ausdrucks a ist   Skolemisierung von T. [SLS 6.1]

Dies bedeutet, dass immer dann, wenn für einen Ausdruck oder Teilausdruck der Typ T[A] forSome {type A} festgelegt ist, ein neuer Typname A1 ausgewählt wird und der Ausdruck den Typ T[A1] erhält. Dies ist sinnvoll, da T[A] forSome {type A} intuitiv bedeutet, dass es einen Typ A gibt, so dass der Ausdruck den Typ T[A] hat. (Welcher Name gewählt wird, hängt von der Compilerimplementierung ab. Ich verwende A1 , um ihn von der gebundenen Typvariablen A zu unterscheiden)

Wir sehen uns die erste Codezeile an:

%Vor%

Hier wird die Skolemisierungsregel tatsächlich noch nicht benutzt. ({ a: Int => a.toString }, 19) hat den Typ (Int=>String, Int) . Dies ist ein Subtyp von (A => String, A) forSome { type A } , da es eine A (nämlich Int ) gibt, so dass die rhs vom Typ (A=>String,A) ist.

Der Wert pair hat jetzt den Typ (A => String, A) forSome { type A } .

Die nächste Zeile ist

%Vor%

Nun ordnet der Typ den Unterausdrücken Typen von innen nach außen zu. Zuerst wird dem ersten Vorkommen von pair ein Typ zugewiesen. Erinnern Sie sich, dass pair den Typ (A => String, A) forSome { type A } hatte. Da die Skolemisierungsregel für jeden Unterausdruck gilt, wenden wir sie auf die erste pair an. Wir wählen einen neuen Typ namens A1 und geben pair als (A1 => String, A1) ein. Dann weisen wir dem zweiten Vorkommen von pair einen Typ zu. Wiederum gilt die Skolemisierungsregel, wir wählen einen anderen frischen Typnamen A2 , und das zweite Vorkommen von pair ist types als (A2=>String,A2) .

Dann hat pair._1 den Typ A1=>String und pair._2 hat den Typ A2 , daher ist pair._1(pair._2) nicht gut typisiert.

Beachten Sie, dass es nicht der "Fehler" der Skolemisierungsregel ist, dass die Eingabe fehlschlägt. Wenn wir die Skolemisierungsregel nicht hätten, würde pair._1 als (A=>String) forSome {type A} und pair._2 als A forSome {type A} eingeben, was dasselbe ist wie Any . Und dann wäre pair._1(pair._2) immer noch nicht gut typisiert. (Die Skolemisierungsregel ist tatsächlich hilfreich, um die Dinge zu typisieren, wir werden das unten sehen.)

Also, warum weigert sich Scala zu verstehen, dass die beiden Instanzen von pair tatsächlich vom Typ (A=>String,A) für die gleiche A sind? Ich kenne keinen guten Grund im Falle von val pair , aber wenn wir zum Beispiel einen var pair vom gleichen Typ haben, darf der Compiler mehrere Vorkommen desselben mit demselben A1 nicht skolemisieren. Warum? Stellen Sie sich vor, dass sich der Inhalt von pair innerhalb eines Ausdrucks ändert. Zuerst enthält es ein (Int=>String, Int) und dann gegen Ende der Auswertung des Ausdrucks enthält es (Bool=>String,Bool) . Dies ist in Ordnung, wenn der Typ von pair (A=>String,A) forSome {type A} ist. Aber wenn der Computer beide Vorkommen von pair den gleichen skolemisierten Typ (A1=>String,A1) geben würde, wäre die Typisierung nicht korrekt. Ähnliches gilt, wenn pair ein def pair wäre, könnte es bei verschiedenen Aufrufen andere Ergebnisse liefern und daher nicht mit demselben A1 skolemisiert werden. Für val pair gilt dieses Argument nicht (da sich val pair nicht ändern kann), aber ich gehe davon aus, dass das Typsystem zu kompliziert wird, wenn es versuchen würde, eine val pair von einer var pair zu unterscheiden. (Es gibt auch Situationen, in denen ein val den Inhalt ändern kann, nämlich von unitialisiert zu initialisiert. Aber ich weiß nicht, ob das in diesem Zusammenhang zu Problemen führen kann.)

Allerdings können wir die Skolemisierungsregel verwenden, um pair._1(pair._2) gut typisiert zu machen. Ein erster Versuch wäre:

%Vor%

Warum sollte das funktionieren? pair types als (A=>String,A) forSome {type A} . Daher wird der Typ (A3=>String,A3) für einige frische A3 . Daher sollte der neue val pair2 den Typ (A3=>String,A3) (den Typ der rhs) erhalten. Und wenn pair2 den Typ (A3=>String,A3) hat, dann wird pair2._1(pair2._2) gut typisiert. (Keine Existenzen mehr beteiligt.)

Leider wird dies aufgrund einer anderen Regel in der Spezifikation nicht funktionieren:

  

Wenn die Wertdefinition nicht rekursiv ist, kann der Typ T weggelassen werden.   in diesem Fall wird der gepackte Ausdruck e angenommen. [SLS 4.1]

Der gepackte Typ ist das Gegenteil von Skolemisierung. Das bedeutet, dass alle frischen Variablen, die aufgrund der Skolemisierungsregel in den Ausdruck eingefügt wurden, nun wieder in existentielle Typen umgewandelt werden. Das heißt, T[A1] wird T[A] forSome {type A} .

Also, in

%Vor%

pair2 wird tatsächlich den Typ (A=>String,A) forSome {type A} erhalten, obwohl die rhs den Typ (A3=>String,A3) erhalten hat. Dann wird pair2._1(pair2._2) nicht wie oben beschrieben eingeben.

Aber wir können einen anderen Trick verwenden, um das gewünschte Ergebnis zu erzielen:

%Vor%

Auf den ersten Blick ist dies eine sinnlose Musterübereinstimmung, da pair2 nur pair zugewiesen wurde. Warum also nicht einfach pair ?Der Grund dafür ist, dass die Regel aus SLS 4.1 nur auf val s und var s angewendet wurde. Variable Muster (wie pair2 hier) sind nicht betroffen. Daher wird pair als (A4=>String,A4) und pair2 als gleicher Typ (nicht als gepackter Typ) eingegeben. Dann wird pair2._1 typisiert A4=>String und pair2._2 ist typisiert A4 und alles ist gut typisiert.

Also kann ein Code-Fragment der Form x match { case x2 => verwendet werden, um x auf einen neuen "Pseudo-Wert" x2 zu "upgraden", was einige Ausdrücke gut typisiert, die nicht gut typisiert werden %Code%. (Ich weiß nicht, warum die Spezifikation nicht einfach das Gleiche zulässt, wenn wir x schreiben. Es wäre sicherlich angenehmer zu lesen, da wir keine zusätzliche Einrückungsebene erhalten.)

Lasst uns nach dieser Exkursion die folgenden Ausdrücke aus der Frage eingeben:

%Vor%

Hier der Ausdruck val x2 = x types als ({ a: Int => a.toString }, 19) . Der Typ case macht dies zu einem Ausdruck des Typs %Code%. Dann wird die Skolemisierungsregel angewendet, also erhält der Ausdruck (das Argument von (Int=>String,Int) ) den Typ (A => String, A) forSome { type A }) für ein neues Wrap . Wir wenden (A5=>String,A5) darauf an, und dass die rhs den Typ A5 hat. Um den Typ von Wrap zu erhalten, müssen wir die Regel aus SLS 4.1 erneut anwenden: Wir berechnen den gepackten Typ von Wrap[(A5=>String,A5)] , der wrap ist. Also Wrap[(A5=>String,A5)] hat den Typ Wrap[(A=>String,A)] forSome {type A} (und nicht wrap , wie man es auf den ersten Blick erwarten würde!) Beachten Sie, dass Wrap[(A=>String,A)] forSome {type A} diesen Typ hat, indem Sie den Compiler mit der Option Wrap[(A=>String,A) forSome {type A}] ausführen.

Wir tippen jetzt

%Vor%

Hier gilt die Skolemisierungsregel für beide Vorkommen von wrap , und sie werden als -Xprint:typer bzw. wrap eingegeben. Dann hat Wrap[(A6=>String,A6)] den Typ Wrap[(A7=>String,A7)] und wrap.x._1 den Typ A6=>String . Daher ist wrap.x._2 nicht gut typisiert.

Aber der Compiler stimmt nicht zu und akzeptiert A7 ! Keine Ahnung warum. Entweder gibt es eine zusätzliche Regel im Scala-System, von der ich nichts weiß, oder es ist einfach ein Compiler-Bug. Das Ausführen des Compilers mit wrap.x._1(wrap.x._2) gibt auch keinen zusätzlichen Einblick, da die Unterausdrücke in wrap.x._1(wrap.x._2) nicht mit Anmerkungen versehen werden.

Weiter ist:

%Vor%

Hier hat -Xprint:typer den Typ wrap.x._1(wrap.x._2) und skolemisiert sich auf pair . Dann hat (A=>String,A) forSome {type A} den Typ (A8=>String,A8) und Wrap(pair) den gepackten Typ Wrap[(A8=>String,A8)] . Das heißt, wrap2 hat denselben Typ wie Wrap[(A=>String,A)] forSome {type A} .

%Vor%

Wie bei wrap2 sollte dies nicht tippen, tut es aber.

%Vor%

Hier sehen wir eine neue Regel: [ SLS 4.1 ] (nicht der oben genannte Teil) erklärt, dass eine solche Musterübereinstimmung wrap -Anweisung auf Folgendes erweitert wird:

%Vor%

Jetzt können wir sehen, dass wrap.x._1(wrap.x._2) den Typ val für frische (a2,b2) bekommt, (A9=>String,A9) erhält den Typ A9 aufgrund der Regel für den gepackten Typ. Dann erhält tmp den Typ (A=>String,A) forSome A mit der Skolemisierungsregel und tmp._1 den Typ A10=>String mit der Regel des gepackten Typs. Und val a2 erhält den Typ (A=>String) forSome {type A} mit der Skolemisierungsregel und tmp._2 erhält den Typ A11 mit der Regel des gepackten Typs (das ist das gleiche wie val b2 ).

Also

%Vor%

ist nicht gut typisiert, weil A forSome {type A} den Typ Any und a2 den Typ A12=>String von der Skolemisierungsregel erhält.

Ähnlich,

%Vor%

wird zu

erweitert %Vor%

Dann wird b2 vom Typ gepackter Typ den Typ A13=>String erhalten, und tmp2 und (A=>String,A) forSome {type A} erhalten den Typ val a3 bzw. val b3 (a.k.a. (A=>String) forSome {type A} ).

Dann

%Vor%

ist aus den gleichen Gründen nicht gut typisiert wie A forSome {type A} nicht.

    
Dominique Unruh 01.09.2016, 00:59
quelle

Tags und Links