Skip to content

Instantly share code, notes, and snippets.

@talesin
Created November 12, 2018 08:28
Show Gist options
  • Save talesin/e3a4c4514074ec6d489e9685b09254d9 to your computer and use it in GitHub Desktop.
Save talesin/e3a4c4514074ec6d489e9685b09254d9 to your computer and use it in GitHub Desktop.
Example of using reflection with discriminated unions in F#
module DiscriminatedUnionsReflection
open System
open System.ComponentModel
open Xunit
open FSharpPlus
open Microsoft.FSharp.Reflection
type States =
| [<DisplayName("Australian Capital Territory")>] ACT
| NSW
| NT
| QLD
| SA
| TAS
| [<DisplayName("Victoria")>][<Description("Founded 1851")>] VIC
| WA
let tryResult f =
try
Ok <| f ()
with
| e -> Error e
let tryOption f = tryResult f |> Option.ofResult
let tryCast<'a> (a:obj) = tryOption (fun () -> a :?> 'a)
let toString (case: 'a) =
tryOption (fun () -> FSharpValue.GetUnionFields(case, typeof<'a>))
|> map (fun (x, _) -> x.Name)
let fromString<'a> case =
tryOption (fun () -> FSharpType.GetUnionCases typeof<'a>)
>>= Array.tryFind (fun x -> x.Name = case)
|> map (fun x -> FSharpValue.MakeUnion(x,[||]) :?> 'a)
let getAttributes<'a, 'b when 'b :> Attribute> (case: 'a) =
tryOption (fun () -> FSharpValue.GetUnionFields(case, typeof<'a>))
|> map (fun (x, _) ->
x.GetCustomAttributes(typeof<'b>)
|> Array.map tryCast<'b>
|> Array.choose id)
let getDisplayName (case: 'a) =
getAttributes<'a, DisplayNameAttribute> case
>>= Array.tryHead
|> map (fun x -> x.DisplayName)
let getDescription (case: 'a) =
getAttributes<'a, DescriptionAttribute> case
>>= Array.tryHead
|> map (fun x -> x.Description)
[<Fact>]
let ``Union case to some of expected string`` () =
Assert.Equal(Some "ACT", toString ACT)
[<Fact>]
let ``Non union value to none`` () =
Assert.Equal(None, toString "fred")
[<Fact>]
let ``Valid case string to some of union case`` () =
Assert.Equal(Some ACT, fromString "ACT")
[<Fact>]
let ``Invalid case string to none`` () =
Assert.Equal(None, fromString<States> "goonabr")
[<Fact>]
let ``Display name returned for union case`` () =
Assert.Equal(Some "Australian Capital Territory", getDisplayName ACT)
[<Fact>]
let ``None returned for union case without display name`` () =
Assert.Equal(None, getDisplayName NSW)
[<Fact>]
let ``Union case with two attributes returns first`` () =
Assert.Equal(Some "Victoria", getDisplayName VIC)
[<Fact>]
let ``Union case with two attributes returns second`` () =
Assert.Equal(Some "Founded 1851", getDescription VIC)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment