36

How can I enumerate through the possible "values" of a discriminated union in F#?

I want to know if is there something like Enum.GetValues(Type) for discriminated unions, tough I am not sure over what kind of data I would enumerate. I would like to generate a list or array of a discriminated union with one item for each option.

michelpm
  • 1,705
  • 2
  • 17
  • 31

4 Answers4

43

Yes, F# has it's own reflection layer build on top of .NET's reflection to help you make sense of types that are specific to F#, like discriminating unions. Here's the code that will let you enumerate a union's cases:

open Microsoft.FSharp.Reflection

type MyDU =
    | One
    | Two
    | Three

let cases = FSharpType.GetUnionCases typeof<MyDU>

for case in cases do printfn "%s" case.Name
Robert
  • 6,198
  • 2
  • 31
  • 40
  • 5
    If you want to get the cases regardless of access restrictions, do not forget to pass `BF.Public ||| BF.NonPublic` where `BF=System.Reflection.BindingFlags` to `GetUnionCases`. – t0yv0 Aug 09 '11 at 14:26
10

If your discriminated union is only made of plain identifiers (no case storing any data, this might be what you need: gist

open Microsoft.FSharp.Reflection

module SimpleUnionCaseInfoReflection =

  // will crash if 'T contains members which aren't only tags
  let Construct<'T> (caseInfo: UnionCaseInfo)                   = FSharpValue.MakeUnion(caseInfo, [||]) :?> 'T

  let GetUnionCaseInfoAndInstance<'T> (caseInfo: UnionCaseInfo) = (caseInfo, Construct<'T> caseInfo)

  let AllCases<'T> = 
    FSharpType.GetUnionCases(typeof<'T>)
    |> Seq.map GetUnionCaseInfoAndInstance<'T>
#load "SimpleUnionCaseInfoReflection.fs"

type Foos = Foo | Bar | Baz
SimpleUnionCaseInfoReflection.AllCases<Foos> |> Seq.iter (fun (caseInfo, instance) ->printfn "name: %s instance: %O is Bar? : %b" caseInfo.Name instance (instance.Equals(Foos.Bar)))

(*
> name: Foo instance: FSI_0055+Foos is Bar? : false
> name: Bar instance: FSI_0055+Foos is Bar? : true
> name: Baz instance: FSI_0055+Foos is Bar? : false
*)
smoothdeveloper
  • 1,817
  • 18
  • 18
10

To slightly extend Robert's example - even if you don't have an instance of the discriminated union, you can use F# reflection to get the information about the type (such as types of the arguments of individual cases). The following extends Robert's sample ans it also prints the types of arguments:

open Microsoft.FSharp.Reflection

let ty = typeof<option<int>>
let cases = FSharpType.GetUnionCases ty

printfn "type %s =" ty.FullName
for case in cases do 
  printf "| %s" case.Name 
  let fields = case.GetFields()
  if fields.Length > 0 then
    printf " of"
  for fld in fields do
    printf " %s " fld.PropertyType.FullName
  printfn ""

For example, for option<int> type, you'll get (I slightly simplified the output):

type Microsoft.FSharp.Core.FSharpOption`1[System.Int32] =
  | None
  | Some of System.Int32

There are many interesting uses for this information - for example, you could generate DB schema from F# unions or to create functions that will parse XML into a discriminated union (that describes the structure). I talked about the XML processing sample at GOTO conference earlier this year.

Tomas Petricek
  • 225,798
  • 19
  • 345
  • 516
  • Thank you for the amazing article (and book). Found there what I was trying to do: https://github.com/tpetricek/Documents/blob/master/Talks%202011/Data%20Access%20(GOTO%20Copenhagen)/GotoDemos/XmlStructural/FSharpWeb.Core/StructuralXml.fs#L68 – michelpm Aug 09 '11 at 20:39
  • @michelpm - Thanks! I'm glad that the samples were useful :-) – Tomas Petricek Aug 09 '11 at 22:30
2

It's hard to see how this could possibly work without also having an instance since discrimination unions can carry values.

If you had a type like this for example:

type Status = Success of string | Error of System.Exception | Timeout

What would you except your array to contain for Success or Error in this case?

alun
  • 3,293
  • 19
  • 24
  • Isn't this what Microsoft.FSharp.Reflection.FSharpType and Microsoft.FSharp.Reflection.FSharpValue are for? – Robert Aug 09 '11 at 13:51
  • If you also have an instance then sure, but to me the question seem to be about the case where you only have the type (Status in this case) but no instance of it. I can't see how you could get anything useful beyond an array of Type in this case. Could be just me misunderstanding the question though. – alun Aug 09 '11 at 13:53
  • Interesting question, I don't really know what I can do with that. Most of the cases if I know what to put there wouldn't help much use discriminated union (something like a enum or a record would probably fit better). – michelpm Aug 09 '11 at 14:02
  • 2
    Sure this is useful even if you don't have an instance! F# reflection gives you information about the _type_ (so you'll get tag names and types of the carried arguments). You can use this information to generate DB schema from F# unions or to create functions that will parse XML into a discriminated union (that describes the structure). See for example http://tomasp.net/blog/goto-loosely-structured-data.aspx – Tomas Petricek Aug 09 '11 at 14:06