2

Let's say I have a discriminated union:

type card = Smithy | Cellar | Chapel

And a function that maps the discriminated union to some value:

let initialCardCount = function
  Smithy -> 4
  Cellar -> 6
  Chapel -> 2

I want to generate a map containing the cost for each member of the union, like:

[(Smithy, 4); (Cellar, 6); (Chapel, 2)]

Is this possible? I'd like to be able to do something like:

List.zip (allValues f) (List.map getCost (allValues f)) |> Map.ofList

This question is helpful but it's not quite what I need; I want to be able to access the union members in memory instead of just inspecting their properties.

The reason I want to do this is so I can have a discriminated union representing all the different types of cards in a game, and a function that tells you how many of each card should be there at the start, and then easily generate the initial mapping of cards to counts. Is there a better way to do this?

Community
  • 1
  • 1
Nick Heiner
  • 108,809
  • 177
  • 454
  • 689
  • 1
    What would you do if one of your discriminated union cases was `T of string`? Then you need to come up with a constructor, and conceivably the `string` could be used in `getCost`. At this point things become quite complicated - perhaps you could tell us what you are trying to use this for? – John Palmer Jun 03 '12 at 03:14
  • 1
    I don't understand what you're asking, but nonetheless I'm looking forward to seeing some blog/app about Dominion using F# :) – Brian Jun 03 '12 at 04:47

1 Answers1

5

You can get a list of all possible discriminated union cases using F# Reflection, but it is important to understand the drawbacks of Reflection - unless used carefuly, it can easily lead to a poor design (there may be simpler and more elegant functional solution) and it is also less efficient (though you probably only need to get the list of all cases just once).

The following getAllValues function gets all cases - it assumes that none of the cases store any value (i.e. Smithy of string will not work!) If you wanted to pass some argument to the cases, you need to pass boxed value in the second argument of MakeUnion:

open Microsoft.FSharp.Reflection

let getAllValues<'T>() : 'T list =
  let cases = FSharpType.GetUnionCases(typeof<'T>)
  [ for c in cases -> unbox (FSharpValue.MakeUnion(c, [| |])) ]

Here is an example that uses the function in your scenario:

type card = Smithy | Cellar | Chapel 

let initialCardCount = function 
  | Smithy -> 4 
  | Cellar -> 6 
  | Chapel -> 2 

let values = getAllValues<card>()
List.zip values (List.map initialCardCount values)
Tomas Petricek
  • 225,798
  • 19
  • 345
  • 516