Existe-t-il un moyen d'utiliser Modèle de test de type et Record Pattern en ligne?
Je peux créer un Record Pattern sans problème comme ceci:
type IA = abstract ID: string type Stage = | FirstStep | SecondStep type A = { id: string name: string option stage: Stage } interface IA with member this.ID = this.id // This is a "nested" pattern inline, I match two Option with one match let tryGetName (a: A option) = match a with | Some { name = (Some name) } -> Some name | _ -> None // This is a more involved nested pattern inline let tryGetStageAndName (a: A option) = match a with | Some { name = (Some name); stage = stage } -> Some (stage, name) | _ -> None // This is the syntax I'm looking for: let tryGetStageAndName2 (a: IA option) = match a with // notice Some (:? A as a) -> is perfectly valid | Some (:? A ({ name = (Some name); stage = stage }) -> Some (stage, name) | _ -> None
Et c'est un code parfaitement valide: p >
let getName2 (a: IA) = match a with | :? A ({name = name}) as a -> name // Type Test Pattern with Record Pattern inline, maybe even still use binding (as a) | _ -> ""
Voici ce dont je parle:
type IA = abstract ID: string type A = { id: string name: string } interface IA with member this.ID = this.id let getName (a: IA) = match a with | :? A as a -> a.name | _ -> "" getName { id = "1234" name = "andrew" } // val getName : a:IA -> string // val it : string = "andrew"
Mettre à jour
Mon exemple précédent est trop simple, utilisez plutôt ce qui suit:
let getName3 (a:A) = match a with | { name = name } -> name
Je veux aussi clarifier, ma question concerne la syntaxe F #, pas les scénarios ad-hoc ou l'encadrement autour du type A
spécifique, comme nous pouvons le faire avec des modèles en ligne imbriqués, y a-t-il un moyen de créer des modèles après un modèle de test de type?
3 Réponses :
Vous pouvez utiliser Active Pattern. L'idée est de convertir le modèle de test de type en un autre modèle dans lequel la valeur transtypée participe au modèle, afin que nous puissions y imbriquer le modèle d'enregistrement.
Le code serait:
let (|IsA|_|) (a: IA) = match a with | :? A as a -> Some a | _ -> None let getName2 (a: IA) = match a with | IsA {name = name} -> name | _ -> ""
Je pense que Nghia Bui a trouvé une très bonne solution. Je voulais juste ajouter que vous voudrez peut-être rendre le modèle actif générique, pour gérer plus de cas. J'ai renommé le cas du modèle actif en Unbox
, ce qui n'est peut-être pas tout à fait exact. Quoi qu'il en soit, voyez cet exemple:
type IA = abstract ID: string type A = { id: string name: string } interface IA with member this.ID = this.id type B = { id: string name: string email: string } interface IA with member this.ID = this.id let inline (|Unbox|_|)<'T when 'T :> IA> (a: IA) = match a with | :? 'T as a -> Some a | _ -> None let getName (a: IA) = match a with | Unbox {A.name = name} -> name | Unbox {B.name = name} -> name | _ -> "" getName {id = "123"; name = "A name"} getName {id = "567"; name = "B name"; email = "abc@123.com"}
Ceci est actuellement une proposition dans fslang-suggestions:
https://github.com/fsharp/fslang-suggestions/issues/830
Cette syntaxe n'existe pas encore
Y a-t-il une raison pour laquelle vous ne pouvez pas simplement ajouter une propriété
Name
ou une méthodeGetName ()
à l'interface et l'implémenter dans le cadre de la définition de type plutôt que dans une fonction distincte? C'est exactement le genre de comportement que les interfaces sont censées abstraire!L'utilisation de
:?
est extrêmement nauséabonde. Je recommanderais fortement d'éviter l'utilisation de tests de type dans le code F #. Vous devez restructurer votre code pour éviter d'avoir à tester si unIA
est unA
.Sinon, comment travailleriez-vous avec les interfaces?
Pourquoi devriez-vous travailler avec des interfaces? Essayez peut-être une union discriminée
type AB = A de A | B de B
à la place, lorsqu'on leur donne deux enregistrementsA
etB
avec un champname
chacun. La déconstruction serait simple dans les modèles, par exemplelet getName = fonction A {nom = nom} | B {name = name} -> name