Automatische Verallgemeinerung

F# verwendet Typrückschlüsse, um die Typen von Funktionen und Ausdrücken auszuwerten. In diesem Thema wird beschrieben, wie F# die Argumente und Typen von Funktionen automatisch generalisiert, sodass sie nach Möglichkeit mit mehreren Typen funktionieren.

Automatische Verallgemeinerung

Der F#-Compiler bestimmt beim Ausführen eines Typrückschlusses für eine Funktion, ob ein bestimmter Parameter generisch sein kann. Der Compiler untersucht jeden Parameter und bestimmt, ob die Funktion eine Abhängigkeit vom spezifischen Typ dieses Parameters aufweist. Falls nicht, ergibt der Rückschluss einen generischen Typ.

Das folgende Codebeispiel veranschaulicht eine Funktion, von der der Compiler schließt, dass sie generisch ist.

let max a b = if a > b then a else b

Der Rückschluss des Typs ergibt 'a -> 'a -> 'a.

Der Typ gibt an, dass es sich um eine Funktion handelt, die zwei Argumente desselben unbekannten Typs verwendet und einen Wert desselben Typs zurückgibt. Einer der Gründe, warum die vorherige Funktion generisch sein kann, ist, dass der Größer-als-Operator (>) selbst generisch ist. Der Größer-als-Operator hat die Signatur 'a -> 'a -> bool. Nicht alle Operatoren sind generisch. Wenn der Code in einer Funktion einen Parametertyp zusammen mit einer nicht-generischen Funktion oder einem nicht-generischen Operator verwendet, kann dieser Parametertyp nicht generalisiert werden.

Da max generisch ist, ist eine Verwendung mit Typen wie int, float usw. möglich, wie in den folgenden Beispielen gezeigt.

let biggestFloat = max 2.0 3.0
let biggestInt = max 2 3

Die beiden Argumente müssen jedoch denselben Typ haben. Die Signatur ist 'a -> 'a -> 'a, nicht 'a -> 'b -> 'a. Daher generiert der folgende Code einen Fehler, da die Typen nicht übereinstimmen.

// Error: type mismatch.
let biggestIntFloat = max 2.0 3

Die max-Funktion funktioniert auch mit jedem Typ, der den Größer-als-Operator unterstützt. Daher können Sie sie auch für eine Zeichenfolge verwenden, wie im folgenden Code gezeigt.

let testString = max "cab" "cat"

Werteinschränkung

Der Compiler führt die automatische Generalisierung nur bei vollständigen Funktionsdefinitionen mit expliziten Argumenten und bei einfachen unveränderlichen Werten durch.

Dies bedeutet, dass der Compiler einen Fehler meldet, wenn Sie versuchen, Code zu kompilieren, der nicht ausreichend auf einen bestimmten Typ eingeschränkt ist, aber auch nicht generalisierbar ist. In der Fehlermeldung zu diesem Problem wird diese Einschränkung der automatischen Generalisierung für Werte als Werteinschränkung genannt.

In der Regel tritt der Werteinschränkungsfehler entweder auf, wenn ein Konstrukt generisch sein soll, der Compiler aber nicht genügend Informationen hat, um es zu generalisieren, oder wenn Sie bei einem nicht generischen Konstrukt versehentlich keine ausreichenden Typinformationen angegeben haben. Die Lösung für den Werteinschränkungsfehler besteht darin, explizitere Informationen bereitzustellen, um das Typrückschlussproblem umfassender einzuschränken, und zwar auf eine der folgenden Arten:

  • Schränken Sie einen Typ so ein, dass er nicht generisch ist, indem Sie einem Wert oder Parameter eine explizite Typanmerkung hinzufügen.

  • Wenn das Problem darin besteht, dass ein nicht generalisierbares Konstrukt zum Definieren einer generischen Funktion verwendet wird, wie z. B. eine Funktionszusammensetzung oder unvollständig angewandte Argumente einer Funktion mit Currying, versuchen Sie, die Funktion als herkömmliche Funktionsdefinition neu zu schreiben.

  • Wenn das Problem ein Ausdruck ist, der für eine Generalisierung zu komplex ist, wandeln Sie ihn in eine Funktion um, indem Sie einen zusätzlichen, nicht verwendeten Parameter hinzufügen.

  • Fügen Sie explizite generische Typparameter hinzu. Diese Option wird selten verwendet.

In den folgenden Codebeispielen werden diese Szenarien veranschaulicht.

Fall 1: Zu komplexer Ausdruck. In diesem Beispiel ist die Liste counter als int option ref gedacht, aber nicht als einfacher unveränderlicher Wert definiert.

let counter = ref None
// Adding a type annotation fixes the problem:
let counter : int option ref = ref None

Fall 2: Verwenden eines nicht generalisierbaren Konstrukts zum Definieren einer generischen Funktion. In diesem Beispiel ist das Konstrukt nicht generalisierbar, da eine partielle Anwendung von Funktionsargumenten vorkommt.

let maxhash = max << hash
// The following is acceptable because the argument for maxhash is explicit:
let maxhash obj = (max << hash) obj

Fall 3: Hinzufügen eines zusätzlichen, nicht verwendeten Parameters. Da dieser Ausdruck für die Generalisierung nicht einfach genug ist, meldet der Compiler den Werteinschränkungsfehler.

let emptyList10 = Array.create 10 []
// Adding an extra (unused) parameter makes it a function, which is generalizable.
let emptyList10 () = Array.create 10 []

Fall 4: Hinzufügen von Typparametern.

let arrayOf10Lists = Array.create 10 []
// Adding a type parameter and type annotation lets you write a generic value.
let arrayOf10Lists<'T> = Array.create 10 ([]:'T list)

Im letzten Fall wird der Wert eine Typfunktion, die verwendet werden kann, um Werte verschiedener Typen zu erstellen, z. B. wie folgt:

let intLists = arrayOf10Lists<int>
let floatLists = arrayOf10Lists<float>

Siehe auch