Marko Grdinić
Marko Grdinić

Reputation: 4062

Is it possible to get the tag of an union at runtime?

I am working on a new version of the Spiral language and as one of its features, it will have structural equality and comparison similar to F#. But I am having difficulty figuring out how to compile that efficiently to F#.

The problem is that I cannot find how F# exposes tag information for its union cases.

For example, if I wrote cmp a b I'd like the Spiral compiler to generate something like the following F# code.

type T =
    | A of int32
    | B of float

let cmp_t = function
    | A x, A x' -> cmp_i32 (x, x')
    | B x, B x' -> cmp_f64 (x, x')
    | x, x' -> cmp_i32 (union_tag x, union_tag x')

I've looked around and there are some proposals to expose the tag information, but nothing is out yet. I am wondering whether the F# compiler library has some functionality that I am not aware of for extracting the tag.

Otherwise for every union type, I am going to have to generate a custom tag function like...

let tag_t = function
    | A _ -> 0
    | B _ -> 1

But this can't be the right choice because F# has to be using some kind of tag under the hood for each of the union cases in order to differentiate them. It has to be able to know the tag for its own comparison function. Making tag functions like these would bloat the generated code and would be inefficient at runtime.

What should I do here?

Upvotes: 2

Views: 136

Answers (1)

Fyodor Soikin
Fyodor Soikin

Reputation: 80805

You can use F# Reflection API for this.

Reflection.FSharpType.GetUnionCases can return you a full list of DU cases, each represented as a UnionCaseInfo structure, which has such useful properties as Name and Tag:

> Reflection.FSharpType.GetUnionCases(typeof<T>) |> Array.map (fun c -> c.Tag)

[|0; 1|]  

And if you have a concrete value of the type (like in your cmp_t function), check out the Reflection.FSharpValue.GetUnionFields function, which, contrary to its name, will return you not only the fields, but also the UnionCaseInfo representing the case:

> let x = A 42
> let caseInfo, fields = Reflection.FSharpValue.GetUnionFields(x, typeof<T>)
> caseInfo.Tag

0

Keep in mind, however, that for F# discriminated unions the compiler will generate structural comparison automatically, so your cmpt_t function is redundant:

> A 42 < A 54
true

> A 3 > B 1.0
false

// Or more generally:
> (A 42 :> System.IComparable<T>).CompareTo(A 54)
-1

So your cmp_t can really be just this:

let cmp_t (x: 'a when 'a :> System.IComparable<'a>) y = 
    (x :> System.IComparable<'a>).CompareTo(y)

> cmp_t (A 42) (A 54)
-1

And behold! There is already such function in the standard library. It's called compare:

> compare (A 42) (A 54)
-1

Upvotes: 4

Related Questions