Généralisation automatique
F# utilise l’inférence de type pour évaluer les types de fonctions et d’expressions. Cette rubrique décrit comment F# généralise automatiquement les arguments et les types de fonctions afin qu’ils fonctionnent avec plusieurs types lorsque cela est possible.
Généralisation automatique
Le compilateur F#, lorsqu’il effectue une inférence de type sur une fonction, détermine si un paramètre donné peut être générique. Le compilateur examine chaque paramètre et détermine si la fonction a une dépendance par rapport au type spécifique de ce paramètre. Si ce n’est pas le cas, le type est interprété comme étant générique.
L’exemple de code suivant illustre une fonction que le compilateur interprète comme étant générique.
let max a b = if a > b then a else b
Le type est interprété comme étant 'a -> 'a -> 'a
.
Le type indique qu’il s’agit d’une fonction qui prend deux arguments du même type inconnu et retourne une valeur de ce même type. L’une des raisons pour lesquelles la fonction précédente peut être générique est que l’opérateur supérieur à (>
) est lui-même générique. L’opérateur supérieur à a la signature 'a -> 'a -> bool
. Tous les opérateurs ne sont pas génériques, et si le code d’une fonction utilise un type de paramètre avec une fonction ou un opérateur non générique, ce type de paramètre ne peut pas être généralisé.
Puisque max
est générique, il peut être utilisé avec des types tels que int
, float
, et ainsi de suite, comme illustré dans les exemples suivants.
let biggestFloat = max 2.0 3.0
let biggestInt = max 2 3
Toutefois, les deux arguments doivent être du même type. La signature est 'a -> 'a -> 'a
, pas 'a -> 'b -> 'a
. Par conséquent, le code suivant génère une erreur, car les types ne correspondent pas.
// Error: type mismatch.
let biggestIntFloat = max 2.0 3
La fonction max
fonctionne également avec n’importe quel type qui prend en charge l’opérateur supérieur à. Par conséquent, vous pouvez également l’utiliser sur une chaîne, comme indiqué dans le code suivant.
let testString = max "cab" "cat"
Restriction de valeur
Le compilateur effectue la généralisation automatique uniquement sur des définitions de fonction complètes qui ont des arguments explicites et sur des valeurs immuables simples.
Cela signifie que le compilateur émet une erreur si vous essayez de compiler du code qui n’est pas suffisamment limité pour être un type spécifique, mais qui n’est pas non plus généralisable. Le message d’erreur pour ce problème fait référence à cette restriction sur la généralisation automatique des valeurs en tant que restriction de valeur.
En règle générale, l’erreur de restriction de valeur se produit soit lorsque vous souhaitez qu’une construction soit générique, mais que le compilateur ne dispose pas d’informations suffisantes pour la généraliser, soit lorsque vous omettez involontairement des informations de type suffisantes dans une construction non générique. La solution à l’erreur de restriction de valeur consiste à fournir des informations plus explicites pour limiter davantage le problème d’inférence de type, de l’une des manières suivantes :
Forcez un type à être non générique en ajoutant une annotation de type explicite à une valeur ou un paramètre.
Si le problème est l’utilisation d’une construction non généralisable pour définir une fonction générique, telle qu’une composition de fonction ou des arguments de fonction curryfiés appliqués de manière incomplète, essayez de réécrire la fonction en tant que définition de fonction ordinaire.
Si le problème est une expression trop complexe pour être généralisée, placez-la dans une fonction en ajoutant un paramètre supplémentaire et inutilisé.
Ajoutez des paramètres de type générique explicites. Cette option est rarement utilisée.
Les exemples de code suivants illustrent chacun de ces scénarios.
Cas 1 : expression trop complexe. Dans cet exemple, la liste counter
est destinée à être int option ref
, mais elle n’est pas définie comme une simple valeur immuable.
let counter = ref None
// Adding a type annotation fixes the problem:
let counter : int option ref = ref None
Cas 2 : utilisation d’une construction non généralisable pour définir une fonction générique. Dans cet exemple, la construction n’est pas généralisable, car elle implique une application partielle d’arguments de fonction.
let maxhash = max << hash
// The following is acceptable because the argument for maxhash is explicit:
let maxhash obj = (max << hash) obj
Cas 3 : ajout d’un paramètre supplémentaire inutilisé. Puisque cette expression n’est pas assez simple pour la généralisation, le compilateur émet l’erreur de restriction de valeur.
let emptyList10 = Array.create 10 []
// Adding an extra (unused) parameter makes it a function, which is generalizable.
let emptyList10 () = Array.create 10 []
Cas 4 : ajout de paramètres de type.
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)
Dans le dernier cas, la valeur devient une fonction de type, qui peut être utilisée pour créer des valeurs de nombreux types différents, par exemple comme suit :
let intLists = arrayOf10Lists<int>
let floatLists = arrayOf10Lists<float>