6

How to enumerate an enum/type in F# tells us how to get an enumerator for a .Net enum type in F#:

Use: Enum.GetValues(typeof<MyType>)

However, when I put that into use I found a limitation. I can work around that limitation, but I am looking for a better way.

The problem is that the solution returns a .Net Array of Objects, but to use it we need to cast it, and that casting is unwieldy for iteration.

type Colors =
| Red = 0
| Blue = 1
| Green = 2

// Strongly typed function, to show the 'obj' in real use
let isFavorite color = color = Colors.Green

// Iterate over the Enum (Colors)
for color in Enum.GetValues(typeof<Colors>) do
    printfn "Color %A. Is Favorite -> %b" color (isFavorite color)  // <-- Need to cast

The (IsFavorite color) raises a type conflict between Colors (expected) and obj (actual)

This is easily fixed:

for obj in Enum.GetValues(typeof<Colors>) do
    printfn "Color %A. Is Favorite -> %b" obj (isFavorite (unbox<Colors> obj))

But, what if one needs that (unbox<Colors> obj) in several places?

A local let color = ... will suffice, but, ideally, we would use an enumerable-expression which returns a seq<Colors>.

I have been able to build that expression, but it is: a. difficult to build, and b. long winded.

let colorsSeq = 
     Seq.cast (Enum.GetValues(typeof<Colors>)) 
     |> Seq.map (fun o -> unbox<Colors> o)

for color in colorsSeq do
    printfn "Color %A. Is Favorite -> %b" color (isFavorite color)

Is there a better expression?

Community
  • 1
  • 1
Stephen Hosking
  • 1,405
  • 16
  • 34

3 Answers3

6

Enum.GetValues is an old BCL API, so there's nothing much you can do about it... I don't think you can get what you want in a significantly more concise way than what you already have, but you can reduce it a bit:

let colorsSeq = Enum.GetValues(typeof<Colors>) |> Seq.cast<Colors>

If you need to do something like this a lot, you could consider packaging it in a generic function:

module Enum = 
    let values<'a> = Enum.GetValues(typeof<'a>) |> Seq.cast<'a>

Which would enable you to use it like this:

for color in Enum.values<Colors> do
    printfn "Color %A. Is Favorite -> %b" color (isFavorite color)
Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
4

This would seem to be about the simplest possible version:

let t : Colors seq = unbox (Enum.GetValues(typeof<Colors>)) 
John Palmer
  • 25,356
  • 3
  • 48
  • 67
  • This is simple enough to put straight into the for loop: `for color in (Enum.GetValues(typeof)) |> unbox do ...`. The type of `unbox` here is unclear. Intellisense infers `obj -> 'T`, however it is clearly unboxing the array, element by element, and somehow inferring the type of the result. – Stephen Hosking Jun 09 '14 at 06:56
  • 1
    I think that the `unbox` uses the fact that the returned type from `GetValues` implements `Ienumerable<'t>` – John Palmer Jun 09 '14 at 07:07
  • @JohnPalmer. The returned type from `GetValues` does not really implement `IEnumerable<'T>`, it implements `IEnumerable` only. You cannot upcast it to `Colors seq` directly, you'd need to downcast to a type wbich does first. That happens to be the F# _array type_, which is somewhat special, since it behaves as it were "an instantiation of a fictitious type definition System.Array" (§5.1.4 spec). Whatever goes on under the hood seems to be more than your explanation suggests. – kaefer Jun 09 '14 at 11:11
  • 1
    @kaefer - are you sure - this seems to suggest that the return value does actually implement the interface - `printfn "%A" ((Enum.GetValues(typeof).GetType() :?> System.Reflection.TypeInfo).ImplementedInterfaces);;` - also see the notes in the remarks here http://msdn.microsoft.com/en-us/library/system.array%28v=vs.110%29.aspx – John Palmer Jun 09 '14 at 11:28
  • @JohnPalmer. No, I'm not. Just noticing that your quote doesn't specify if those hidden interfaces are a CLI feature or a language feature. If the latter case, then F# has a similar feature, one that is at work when you're testing for the presence of such an interface. Consider `(Enum.GetValues typeof).GetType().Name`, which gives `"Colors[]"`. – kaefer Jun 09 '14 at 12:23
  • 1
    @kaefer The interface implementation is not language-specific. The documentation cited is .NET Framework documentation, not C# documentation. All those reflection members (`GetType` and `ImplementedInterfaces`) behave the same regardless of the language from which they are called. – phoog Jun 10 '14 at 19:56
1

The simplest generic expression would be the casting (or unboxing) to the underlying type 'T[]. Thus

module Enum =
    open System
    let cases<'T when 'T :> Enum> = Enum.GetValues typeof<'T> :?> 'T[]

which translates to C# as

public static class Enum
{
    public static T[] cases<T>() where T : Enum
    {
        return (T[])Enum.GetValues(typeof(T));
    }
}

Plugged into the code in the question it gives this output:

type Colors =
| Red = 0
| Blue = 1
| Green = 2
let isFavorite color = color = Colors.Green

for color in Enum.cases<Colors> do
    printfn "Color %A. Is Favorite -> %b" color (isFavorite color)

//Color Red. Is Favorite -> false
//Color Blue. Is Favorite -> false
//Color Green. Is Favorite -> true
kaefer
  • 5,491
  • 1
  • 15
  • 20
  • This can also go straight into the `for` loop, as follows: `Enum.GetValues typeof :?> Colors[]`. I now favor this as the answer, because it is just a type cast on the primitive. However, I'll wait a while before changing my accepted answer - and consider any comments. As this question has raised some discussion, of a detailed technical nature, I'll probably wait a few days. – Stephen Hosking Jun 10 '14 at 22:54