Created
November 12, 2018 08:28
-
-
Save talesin/e3a4c4514074ec6d489e9685b09254d9 to your computer and use it in GitHub Desktop.
Example of using reflection with discriminated unions in F#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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