Frage

Ich nehme einen Kurs über Programmiersprachen und die Antwort auf „wenn eine Funktion ein Untertyp einer anderen Funktion“ ist sehr kontraintuitiv zu mir.

Zur Klarstellung: Angenommen, dass wir die folgende Art Beziehung haben:

bool<int<real

Warum ist (real->bool) die Funktion einen Subtyp von (int->bool)? Sollte es nicht anders herum sein?

würde ich die Kriterien für die Unter Typisierung Funktionen erwarten zu sein: f1 ist ein Subtyp von f2, wenn f2 jedes Argument nehmen, die f1 nehmen, und f1 gibt Werte nur, dass f2 zurückkehrt. Es gibt eindeutig Werte, die f1 nehmen kann, aber f2 kann es nicht.

War es hilfreich?

Lösung

Hier ist die Regel für die Funktion Subtypisierung:

Die Argumente müssen kontravarianten, Rückgabetypen sein muss zusammen Variante sein.

Co-Variante == bewahrt die Hierarchie für die Art des Ergebnisparameters „A ein Subtyp von B ist“.

Contra-Variante == umkehrt ( "geht gegen") die Typenhierarchie für die Argumente Parameter.

Also, in Ihrem Beispiel:

f1:  int  -> bool
f2:  bool -> bool

Wir schließen sicher, dass f2 ein Subtyp von f1 ist. Warum? Weil (1) bei nur die Argumenttypen für beide Funktionen suchen, sehen wir, dass die Art Hierarchie von „Bool ein Subtyp von int“ ist in der Tat Co-Variante. Es bewahrt die Art Hierarchie zwischen ints und bools. (2) Blick auf nur die Ergebnisse Typen für beide Funktionen, sehen wir, dass Kontravarianz aufrechterhalten wird.

Mit anderen Worten (die Ebene Englisch Art, wie ich zu diesem Thema denke):

kontravarianten Argumente: „meine Aufrufer in kann mehr als ich benötige, aber das ist okay, weil ich nur verwenden werde, was ich verwenden müssen“ kovarianten Rückgabewerte: „Ich zurückkehren mehr als der Anrufer erfragt, aber das ist in Ordnung, wird er / sie nur benutzen, was sie brauchen, und den Rest ignorieren“

Die bei anderen Beispielen sehen lassen, structs mit, wo alles eine ganze Zahl ist:

f1:  {x,y,z} -> {x,y}
f2:  {x,y}   -> {x,y,z}

hier also wieder, wir behaupten, dass f2 ist ein Subtyp von f1 (was es ist). Mit Blick auf den Argumenttypen für beiden Funktionen (und mit Hilfe des

Mit Blick auf die Rückgabetypen für beide Funktionen, wenn f2 {x, y, z}? Die Antwort ist wieder ja. (Siehe obige Logik).

Noch ein dritte Weg, um darüber nachzudenken, ist f2

   F1 = f1;
   F2 = f2;
   {a,b}   = F1({1,2,3});  // call F1 with a {x,y,z} struct of {1,2,3};  This works.
   {a,b,c} = F2({1,2});    // call F2 with a {x,y} struct of {1,2}.  This also works.

   // Now take F2, but treat it like an F1.  (Which we should be able to do, 
   // right?  Because F2 is a subtype of F1).  Now pass it in the argument type 
   // F1 expects.  Does our assignment still work?  It does.
   {a,b} = ((F1) F2)({1,2,3});

Andere Tipps

Hier ist eine andere Antwort, weil, während ich verstanden, wie die Funktion Subtyp Regeln sinnvoll, ich wollte eine andere Kombination von Argumenten / Ergebnis Subtyping abarbeiten, warum nicht.


Die Subtyp Regel lautet:

Funktion Subtyping Regel

Was bedeutet, dass, wenn die Top-Subtyp Bedingungen erfüllt sind, dann wird der Boden gilt.

In der Funktion Typdefinition, die Funktion Argumente kontra , da wir die Subtyp Beziehung zwischen T1 und S1 umgekehrt haben. Die Funktion Ergebnisse sind covariant , weil sie die Subtyp Beziehung zwischen T2 und S2 bewahren.

Mit der Definition aus dem Weg, warum ist die Regel, wie diese? Es ist gut in Aaron Fi Antwort erwähnt, und ich fand auch die Definition hier (für die Rubrik "Funktionstypen" suchen):

  

Eine alternative Ansicht ist, dass es sicher ist, eine Funktion eines Typs zu ermöglichen,   S1 → S2 in einem Kontext verwendet werden, in dem ein anderer Typ T1 → T2 erwartet wird   solange keines der Argumente, die es (T1 <: S1) und keines der Ergebnisse in diesem Zusammenhang auf die Funktion übergeben werden überrascht sein, dass es überraschen liefert den Kontext (S2 <: T2).

Auch das machte Sinn für mich, aber ich wollte keine andere Kombination einzutippen Regeln sinnvoll, um zu sehen, warum. Um dies zu tun ich auf eine einfache Funktion höherer Ordnung und einige Beispielsatzarten betrachtet.

Für alle folgenden Beispiele lassen:

  1. S1 := {x, y}
  2. T1 := {x, y, z}
  3. T2 := {a}
  4. S2 := {a, b}

Beispiel mit kontraArgumentTypen und kovarianten Rückgabetypen

Lassen Sie:

  1. f1 haben Typ S1 → S2 ⟹ {x, y} → {a, b}
  2. f2 haben Typ T1 → T2 ⟹ {x, y, z} → {a}

Nun, da type(f1) <: type(f2) nehmen. Wir wissen, dass dies von der Regel oben, aber wir so tun wir nicht und sehen, warum es Sinn macht.

Wir betreiben map( f2 : {x, y, z} → {a}, L : [ {x, y, z} ] ) : [ {a} ]

Wenn wir f2 mit f1 ersetzen wir bekommen:

map( f1 : {x, y} → {a, b}, L : [ {x, y, z} ] ) : [ {a, b} ]

Dies funktioniert gut, weil:

  1. Unabhängig von der Funktion f1 mit seinem Argument der Fall ist, kann es die zusätzlichen z Satzfeld ignorieren und haben keine Probleme.
  2. Was auch immer der Kontext läuft map tut mit den Ergebnissen, kann es ignorieren das zusätzliche b Satzfeld und hat keine Probleme.

Abschließenden:

{x, y} → {a, b} ⟹ {x, y, z} → {a} ✔

Beispiel mit covariant Argumenttypen und covariant Rückgabetypen

Lassen Sie:

  1. f1 haben Typ T1 → S2 ⟹ {x, y, z} → {a, b}
  2. f2 haben Typ S1 → T2 ⟹ {x, y} → {a}

Angenommen type(f1) <: type(f2)

Wir betreiben map( f2 : {x, y} → {a}, L : [ {x, y} ] ) : [ {a} ]

Wenn wir f2 mit f1 ersetzen wir bekommen:

map( f1 : {x, y, z} → {a, b}, L : [ {x, y} ] ) : [ {a, b} ]

Wir können auf ein Problem stoßen hier, weil f1 erwartet und könnte auf dem z Satzfeld arbeiten, und ein solches Feld nicht vorhanden ist in allen Datensätzen in der Liste L. ⚡

Beispiel mit kontraArgumentTypen und kontra Rückgabetypen

Lassen Sie:

  1. f1 haben Typ S1 → T2 ⟹ {x, y} → {a}
  2. f2 haben Typ T1 → S2 ⟹ {x, y, z} → {a, b}

Angenommen type(f1) <: type(f2)

Wir betreiben map( f2 : {x, y, z} → {a, b}, L : [ {x, y, z} ] ) : [ {a, b} ]

Wenn wir f2 mit f1 ersetzen wir bekommen:

map( f1 : {x, y} → {a}, L : [ {x, y, z} ] ) : [ {a} ]

Wir haben kein Problem mit dem z Satzfeld zu ignorieren, wenn sie in f1 vergangen, aber wenn der Kontext, map ruft erwartet, die eine Liste der Datensätze mit einem b Feld, werden wir einen Fehler getroffen. ⚡

Beispiel mit covariant Argumenttypen und kontra Rückkehr

Schauen Sie sich den obigen Beispielen für die beiden Orte könnte dies schief gehen.

Fazit

Dies ist eine sehr lange und ausführliche Antwort, aber ich hatte dieses Zeug aufzuschreiben, warum anderes Argument, um herauszufinden, und die Rück Parameter Subtyping war ungültig. Da ich es niedergeschrieben etwas hatte, dachte ich, warum nicht hier posten.

Die Frage ist beantwortet, aber ich möchte hier ein einfaches Beispiel präsentieren (in Bezug auf das Argument Typ, der die nicht intuitiv ist).

Der Code unten fehl , weil man nur Strings passieren kann myFuncB, und wir sind vorbei Zahlen und Boolesche Werte.

typedef FuncTypeA = Object Function(Object obj); // (Object) => Object
typedef FuncTypeB = String Function(String name); // (String) => String

void process(FuncTypeA myFunc) {
   myFunc("Bob").toString(); // Ok.
   myFunc(123).toString(); // Fail.
   myFunc(true).toString(); // Fail.
}

FuncTypeB myFuncB = (String name) => name.toUpperCase();

process(myFuncB);

Allerdings ist der Code unten funktioniert , denn jetzt können Sie Objekte jeglicher Art passieren myFuncB, und wir nur Strings übergeben.

typedef FuncTypeA = Object Function(String name); // (String) => Object
typedef FuncTypeB = String Function(Object obj); // (Object) => String

void process(FuncTypeA myFuncA) {
   myFunc("Bob").toString(); // Ok.
   myFunc("Alice").toString(); // Ok.
}

FuncTypeB myFuncB = (Object obj) => obj.toString();

process(myFuncB);
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top