Reputation: 4062
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
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