Getting the types in an F# union type II -- the perils of relying on undocumented implementation details
Recalling this example --
open System | |
open Microsoft.FSharp.Reflection | |
let GetUnionTypes (t:Type) = | |
seq { yield t | |
if FSharpType.IsUnion t | |
then yield! FSharpType.GetUnionCases t | |
// implementation detail leaks here -- nested type name | |
|> Seq.map (fun x -> (string t) + "+" + x.Name) | |
|> Seq.map Type.GetType } | |
type expr = Num of int | |
| Operation of (expr * expr) | |
let baseType = typeof<expr> | |
GetUnionTypes baseType | |
|> Seq.iter (printfn "%A") | |
;; | |
It was adequate for the use case I had of it at the time; but then I tried a different union type, at which point a bug showed up
type Card = King | Queen| Jack | Spot of int;; | |
GetUnionTypes typeof<Card> |> Seq.iter (fun x -> printfn "%s" x.FullName);; |
yields
FSI_0003+Card System.NullReferenceException: Object reference not set to an instance of an object. at FSI_0005.it@24-1.Invoke(Type x) at Microsoft.FSharp.Collections.SeqModule.Iterate[T](FSharpFunc`2 action, IEnumerable`1 source) at.$FSI_0005.main@() Stopped due to error
And, of course, that is exactly what I deserved for relying on the undocumented internals.
A quick decompilation reveals this to be because union cases that carry no data are actually concrete base type instances, bearing the tag number as data, like --
internal static readonly Program.Card _unique_King = new Program.Card(0); |
rather than being redundant empty subtypes.
So we need to tweak our function to be
let GetUnionTypes (t:Type) = | |
seq { yield t | |
if FSharpType.IsUnion t | |
then yield! FSharpType.GetUnionCases t | |
// implementation detail leaks here -- nested type name | |
|> Seq.map (fun x -> Type.GetType ((string t) + "+" + x.Name)) | |
// except when the case has no data, so there's no such class | |
|> Seq.filter (fun x -> x <> null) } |
and all is well. For the moment. Until the representation changes.
No comments :
Post a Comment