Coding Edgar
Coding Edgar

Reputation: 1465

Deconstruct inline with Type Testing in Pattern Matching F#

Is there a way to use Type Test Pattern and Record Pattern inline?

I can make Record Pattern without problem like this:

let getName3 (a:A) =
  match a with
  | { name = name } -> name

And this is a perfectly valid code:

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"

This is what I'm talking about:

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

Update

My previous example is too simple, use the following instead:

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

I also want to clarify, my question is about F# Syntax, not ad-hoc scenarios or boxing around the specific type A, as we can do nested inline patterns, is there a way to do patterns after a Type Test Pattern?

Upvotes: 1

Views: 293

Answers (3)

Coding Edgar
Coding Edgar

Reputation: 1465

This is currently a proposal in fslang-suggestions:

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

This syntax does not exist yet

Upvotes: 0

torbonde
torbonde

Reputation: 2459

I think Nghia Bui came up with a pretty good solution. I just wanted to add that you might want to make the active pattern generic, to handle more cases. I have renamed the active pattern case to Unbox, which might not be entirely accurate. Anyway, see this example:

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 = "[email protected]"}

Upvotes: 0

Nghia Bui
Nghia Bui

Reputation: 3784

You can use Active Pattern. The idea is to convert the Type Test Pattern to another pattern in which the casted value is participated in the pattern, so we can nested the Record Pattern into it.

The code would be:

let (|IsA|_|) (a: IA) =
    match a with
    | :? A as a -> Some a
    | _ -> None

let getName2 (a: IA) =
  match a with
  | IsA {name = name} -> name
  | _ -> ""

Upvotes: 3

Related Questions