1
votes

Déconstruire en ligne avec le test de type dans la correspondance de modèles F #

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?


4 commentaires

Y a-t-il une raison pour laquelle vous ne pouvez pas simplement ajouter une propriété Name ou une méthode GetName () à 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 un IA est un A .


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 enregistrements A et B avec un champ name chacun. La déconstruction serait simple dans les modèles, par exemple let getName = fonction A {nom = nom} | B {name = name} -> name


3 Réponses :


3
votes

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
  | _ -> ""


0 commentaires

0
votes

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"}


0 commentaires

0
votes

Ceci est actuellement une proposition dans fslang-suggestions:

https://github.com/fsharp/fslang-suggestions/issues/830

Cette syntaxe n'existe pas encore


0 commentaires