I'm trying to create DU cases from strings. The only way I can see doing this is by enumerating over the DU cases via Microsoft.FSharp.Reflection.FSharpType.GetUnionCases
and then picking the UnionCase
that matches the string (by using .Name
) and then making the actual DU case out of that by using FSharpValue.MakeUnion
.
Isn't there an easier/more elegant way of doing this? In my scenario I have a DU with a couple of hundred cases for keywords. I have to read the strings (keywords) from a file and make the types out of them. I did some "optimization" by putting the cases into a Map but I was hoping there'd be a better way of doing this.
I have the following, for example:
type Keyword =
| FOO
| BAR
| BAZ
| BLAH
let mkKeywords (file: string) =
use sr = new StreamReader(file)
let caseMap =
FSharpType.GetUnionCases(typeof<Keyword>)
|> Array.map (fun c -> (c.Name, FSharpValue.MakeUnion(c, [||]) :?> Keyword))
|> Map.ofArray
[
while not sr.EndOfStream do
let l = sr.ReadLine().Trim()
match caseMap.TryFind l with
| Some c -> yield c
| None -> failwith <| "Could not find keyword: " + l
]
A discriminated union is a union data structure that holds various objects, with one of the objects identified directly by a discriminant. The discriminant is the first item to be serialized or deserialized. A discriminated union includes both a discriminant and a component.
Discriminated Unions are a functional programming convenience that indicates that something is one of several different types of objects. For example, a User might be an unauthenticated user, a regular user, or an administrator.
In F#, a sum type is called a “discriminated union” type. Each component type (called a union case) must be tagged with a label (called a case identifier or tag) so that they can be told apart (“discriminated”). The labels can be any identifier you like, but must start with an uppercase letter.
I found this handy code snippet...
open Microsoft.FSharp.Reflection
let toString (x:'a) =
let (case, _ ) = FSharpValue.GetUnionFields(x, typeof<'a>)
case.Name
let fromString<'a> (s:string) =
match FSharpType.GetUnionCases typeof<'a> |> Array.filter (fun case -> case.Name = s) with
|[|case|] -> Some(FSharpValue.MakeUnion(case,[||]) :?> 'a)
|_ -> None
... which makes it easy to tack on two lines of code to any DU...
type A = X|Y|Z with
override this.ToString() = FSharpUtils.toString this
static member fromString s = FSharpUtils.fromString<A> s
I would use pattern matching like this:
type Keyword =
| FOO
| BAR
| BAZ
| BLAH
let matchKeyword (word:string) : Keyword option =
match word with
| "FOO" -> Some FOO
| "BAR" -> Some BAR
| "BAZ" -> Some BAZ
| "BLAH" -> Some BLAH
| _ -> None
And maybe auto generate the match statement first time using regex in my editor, but only because you have hundreds of cases. But i am not sure if its a better solution then yours.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With