Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Une fonction inline avec des paramètres de type résolus statiquement peut nécessiter un membre portant un nom et une signature particuliers. F# 11.2 permet à un membre d’extension dans l’étendue de satisfaire cette exigence, même lorsque le type provient d’un autre assembly.
Utilisez cette fonctionnalité pour séparer un algorithme générique des adaptateurs pour des types particuliers. Vous n’avez pas besoin d’encapsuler ces types ou d’y ajouter une interface nominale.
Note
Dans le sdk .NET 11.0.100, activez l’emplacement <LangVersion>preview</LangVersion> où le compilateur résout les extensions. Consultez les paramètres de langue F# 11.2 pour la bande de fonctionnalités planifiée. Les exemples de chemin d’accès et de mappage utilisent une bibliothèque F# 11.0 et un consommateur en préversion ; consultez Compiler des bibliothèques et des consommateurs.
Adapter une propriété pour un algorithme partagé
Le code d’application suivant mesure les chemins d’accès représentés par deux types d’infrastructure existants. Les deux chemins ont une longueur de 8:
open System.Numerics
open Structural.Adapters
open Structural.Algorithms
let complexRoute =
[ Complex(0.0, 0.0); Complex(3.0, 4.0); Complex(6.0, 4.0) ]
let vectorRoute =
[ Vector2(0.0f, 0.0f); Vector2(3.0f, 4.0f); Vector2(6.0f, 4.0f) ]
printn (pathLength complexRoute) // 8
printn (pathLength vectorRoute) // 8
La source de bibliothèque définit Structural.Adapters et Structural.Algorithms. Les adaptateurs exposent la même propriété de coordonnées sur les types avec différentes API natives :
type System.Numerics.Complex with
member point.Coordinates = point.Real, point.Imaginary
type System.Numerics.Vector2 with
member point.Coordinates = float point.X, float point.Y
L’algorithme demande une Coordinates propriété retournant une paire de valeurs à virgule flottante :
let inline coordinates (point: ^Point) : float * float =
(^Point : (member Coordinates: float * float) point)
let inline pathLength (points: ^Point list) =
points
|> List.pairwise
|> List.sumBy (fun (a, b) ->
let ax, ay = coordinates a
let bx, by = coordinates b
let dx, dy = bx - ax, by - ay
sqrt (dx * dx + dy * dy))
À chaque appel concret, le compilateur sélectionne un Coordinates membre, appelé témoin de la contrainte. L’algorithme inline accepte une propriété intrinsèque ou d’extension ; les chemins vides et à point unique ont la longueur zéro.
Partager une opération entre les types de collection
Les contraintes d’extension prennent également en charge les membres statiques. Le module de la Structural.Adapters bibliothèque définit des adaptateurs à l’aide des fonctions existantes List.map :Option.map
type List<'T> with
static member Map(source: 'T list, mapping: 'T -> 'U) : 'U list =
List.map mapping source
type Option<'T> with
static member Map(source: 'T option, mapping: 'T -> 'U) : 'U option =
Option.map mapping source
Dans le module de Structural.Algorithms la bibliothèque, demandez un Map membre sur le type d’entrée complet :
let inline map (mapping: 'T -> 'U) (source: ^Input) : ^Output =
(^Input :
(static member Map: ^Input * ('T -> 'U) -> ^Output)
(source, mapping))
L’application peut ensuite partager une transformation, modifier le type d’élément et composer le mappage sur une collection imbriquée :
let inline cleanNames values =
map (fun (name: string) -> name.Trim().ToUpperInvariant()) values
let batch: string list = cleanNames [ " Ada "; " grace " ]
let optional: string option = cleanNames (Some " linus ")
let missing: string option = cleanNames (None: string option)
let lengths: int list = map String.length batch
let nested: int option list = map (map String.length) [ Some "abc"; None ]
printfn "%A; %A; %A" batch optional missing
printfn "%A; %A" lengths nested
// ["ADA"; "GRACE"]; Some "LINUS"; None
// [3; 5]; [Some 3; None]
Le compilateur vérifie les noms et les types de membres, et non les interfaces ou les lois de mappage. Ce n’est pas un polymorphisme de type supérieur : aucun paramètre ne représente un constructeur de conteneur tel que F<_>.
Extensions d’opérateur
Les opérateurs peuvent utiliser le même mécanisme. Par exemple, définissez un opérateur de mappage pour les listes et les options :
type List<'T> with
static member (|>>) (source: 'T list, mapping: 'T -> 'U) =
List.map mapping source
type Option<'T> with
static member (|>>) (source: 'T option, mapping: 'T -> 'U) =
Option.map mapping source
let inline (|>>) (source: ^Input) (mapping: 'T -> 'U) : ^Output =
(^Input :
(static member (|>>): ^Input * ('T -> 'U) -> ^Output)
(source, mapping))
Un consommateur s’ouvre StructuralOperators et utilise l’opérateur :
open StructuralOperators
let incremented: int list = [1; 2; 3] |>> ((+) 10)
let length: int option = Some "hello" |>> String.length
// [11; 12; 13] and Some 5
Les solutions d’opérateur primitive intégrées conservent la priorité pour les types correspondants. Une extension ne fournit pas d’élargissement arbitraire numérique mixte ou ne remplace pas tous les opérateurs intégrés.
Le mode F# 11.0 peut accepter une déclaration d’opérateur d’extension ordinaire avec FS1215, mais ne peut pas l’utiliser comme solution SRTP. Le projet d’opérateur utilise la préversion.
Opérateurs et contexte tuples
Une collection d’observations et une paire de résultats accumulés indépendamment peuvent participer au même protocole de combinaison. Le consommateur suivant définit son algorithme générique après avoir ouvert les adaptateurs de la bibliothèque :
open Composition.Adapters
open Composition.Algorithms
let inline combineAll (batches: ^T list) : ^T =
List.reduce (++) batches
let observations = combineAll [["temperature"]; ["pressure"; "humidity"]]
let summary = combineAll [(2, ["temperature"]); (3, ["pressure"; "humidity"])]
printfn "%A" observations
printfn "%A" summary
// ["temperature"; "pressure"; "humidity"]
// (5, ["temperature"; "pressure"; "humidity"])
Ces extensions de bibliothèque donnent à l’opérateur une signification pour les entiers, les listes et les paires :
type System.Int32 with
static member (++) (left: int, right: int) = left + right
type List<'T> with
static member (++) (left: 'T list, right: 'T list) = left @ right
type ('T1 * 'T2) with
static member inline (++) ((a, b), (c, d)) = (a ++ c, b ++ d)
combineAll nécessite une liste sansmpty, car elle utilise List.reduce. L’opération conserve l’ordre de liste ; le compilateur n’applique pas l’associativité ou ne fournit pas d’identité pour une entrée vide.
En-têtes natifs et type struct ('T1 * 'T2) with étendre les tuples type ('T1 * 'T2) with de référence et de valeur CLR. Les deux sont des fonctionnalités F# 11.2 et prennent en charge deux à sept éléments ; les en-têtes plus volumineux produisent FS3915.
Les tuples peuvent également porter le contexte en même temps qu’une valeur. L’opérateur suivant ajoute une piste lors de l’application d’une fonction contextuelle :
let priced =
(["catalogue price"], fun units -> decimal units * 12m)
<*> (["validated quantity"], 3)
printfn "%A" priced
// (["catalogue price"; "validated quantity"], 36M)
type ('Context * 'Value) with
static member inline (<*>) ((before, mapping), (after, value)) =
(before ++ after, mapping value)
type struct ('Context * 'Value) with
static member inline (<*>) (struct (before, mapping), struct (after, value)) =
struct (before ++ after, mapping value)
Les appels point-instance tuple et les en-têtes de type de fonction natifs tels que type ('a -> 'b) with ne sont pas pris en charge.
La bibliothèque de composition et le consommateur utilisent la préversion. Conserver combineAll dans le consommateur : l’exportation de cette réduction peut produire FS0193 pour tuple ++. Les exemples de chemin d’accès et de mappage distincts exportent leurs algorithmes.
Construction dirigée par retour
Pour une opération qui introduit une valeur brute dans un conteneur, le type de sortie peut identifier le membre qui doit le construire :
type List<'T> with
static member Return(value: 'T) : 'T list = [value]
type Option<'T> with
static member Return(value: 'T) : 'T option = Some value
let inline singleton (value: 'T) : ^Output =
(^Output : (static member Return: 'T -> ^Output) value)
let oneBatch: string list = singleton "ready"
let oneOptional: string option = singleton "ready"
// ["ready"] and Some "ready"
L’annotation de résultat du consommateur sélectionne l’extension du type de Return sortie. L’entrée seule ne peut pas choisir un conteneur.
Chaque conteneur fournit son propre membre. Pour les surcharges de retour uniquement sur un type unique, utilisez l’attribut distinctAllowOverloadOnReturnType.
Compiler des bibliothèques et des consommateurs
Les paramètres linguistiques s’appliquent par projet, et non transitif par le biais de références. La matrice suivante utilise les paramètres du SDK .NET 11.0.100 pour les fonctionnalités F# 11.2 :
| Implémentation de bibliothèque | Utilisation du consommateur | Conditions requises en préversion |
|---|---|---|
| Déclare les en-têtes tuple-extension natifs. | Résout leurs opérateurs d’extension. | Les deux ont besoin d’une préversion ; la nouvelle syntaxe d’en-tête elle-même est en préversion. |
| Définit les extensions ordinaires et exporte une contrainte membre inline non résolue. | Fournit des types concrets dont les extensions résolvent cette contrainte. | Le consommateur a besoin d’une préversion. La bibliothèque peut utiliser F# 11.0. |
| Résout une contrainte d’extension en interne et expose une API inline ordinaire ou déjà résolue. | Appelle cette API sans résolution d’extension supplémentaire. | La bibliothèque a besoin d’une préversion ; le consommateur peut utiliser F# 11.0. |
| Résout les extensions et les contraintes d’exportation que le consommateur résout également par le biais d’extensions. | Résout les contraintes restantes. | Les deux ont besoin d’une préversion. |
Le chemin d’accès et la bibliothèque de mappages utilise 11.0; son consommateur utilise preview. À partir du répertoire consommateur, exécutez-le dotnet run avec un SDK F# 11 prenant en charge.
Étendue d’extension entre assemblys
Utilisez des extensions publiques et ouvrez leur module avant de définir des fonctions inline dépendantes. Pour une contrainte non résolue importée, ouvrez également les adaptateurs dans le consommateur ; les déclarations du open producteur ne sont pas transférées. Une API déjà résolue ne demande pas au consommateur de rouvrir ses adaptateurs.
Inférence et signatures
La résolution F# 11.2 reporte certains choix que le mode F# 11.0 effectue précédemment. Considérez un assistance inline défini avec un DateTime récepteur :
let inline shift (date: System.DateTime) offset = date + offset
En mode F# 11.0, ce formulaire peut déduire DateTime -> TimeSpan -> DateTime. Avec la résolution d’extension F# 11.2 activée, elle peut conserver une contrainte pour (DateTime or ^Offset) : (static member (+) : DateTime * ^Offset -> ^Result). Un consommateur peut ensuite sélectionner une extension publique dans l’étendue pour un autre type de décalage.
Un consommateur ne peut pas récupérer la généralité perdue dans une bibliothèque F# 11.0. Vérifier les signatures exportées ; le producteur peut également avoir besoin d’une inférence F# 11.2 activée.
Limitations et résolution des problèmes en préversion
- Pour FS0001 ou FS0193, vérifiez les paramètres linguistiques, la visibilité publique et l’étendue de l’adaptateur.
- Si un fichier producteur ultérieur résout l’exemple de chemin d’accès pour
Complex, la génération de code peut échouer avec FS0971,Undefined value 'get_Coordinates'. Conservez cette résolution avec ses définitions ou dans un consommateur distinct. - Les adaptateurs concurrents peuvent rendre la sélection de membre ambiguë.