Reputation: 5359
I'm trying to define a generic conversion operator from a string to an Enum, and I'd like to use it like this:
let day = asEnum<DayOfWeek>("Monday")
But with this implementation:
let asEnum<'a, 'b when 'a: (new : unit -> 'a) and 'a : struct and 'a :> ValueType and 'a : enum<'b>> text =
match Enum.TryParse<'a>(text) with
| true, value -> Some value
| false, _ -> None
I can only use it like this:
let day = asEnum<DayOfWeek,_>("Monday")
or this:
let day:DayOfWeek option = asEnum("Monday")
If I omit the 'a : enum<'b>
altogether from the type constraint, I can have it as I want, but then if someone doesn't specify the type it will default to int
, which I really don't like, I'd prefer it to give a compile time error like it does when I specify a constraint
Maybe there's any trick to just specify one type parameter and have the other one infered? Any ideas?
Upvotes: 9
Views: 871
Reputation: 1124
A little update something like 3 years afterwards ^_^
Using this string extension to convert string to an enum
type System.String with
/// Strongly-typed shortcut for Enum.TryParse().
member this.ToEnum<'a when 'a :> System.Enum and 'a : struct and 'a : (new: unit -> 'a)> () =
let ok, v = System.Enum.TryParse<'a>(this, true)
if ok then Some v else None
Be careful with your enum declaration.
type failingEnum =
| FirstValue
| SecondValue
| AnotherValue
type compliantEnum =
| FirstValue = 0
| SecondValue = 1
| AnotherValue = 2
Then
let x = "whatever".ToEnum<failingEnum>();
//- will give error failingEnum is not compatible with the type System.Enum
let x = "whatever".ToEnum<compliantEnum>();
//- will succeed !
Upvotes: 1
Reputation: 47904
Unfortunately, in order to augment the constraint it seems you have to spell it all out: (as kvb pointed out, you can avoid duplicating the constraints on TryParse
by adding the 'T : enum<int>
constraint outside the angle brackets)
This also works:
let asEnum<'T
when 'T : enum<int>
and 'T : struct
and 'T :> ValueType
and 'T : (new : unit -> 'T)> text =
match Enum.TryParse<'T>(text) with
| true, value -> Some value
| _ -> None
This gives a compile-time error if the underlying type isn't int
:
type ByteEnum =
| None = 0uy
asEnum<ByteEnum> "None" //ERROR: The type 'int' does not match the type 'byte'
Upvotes: 4
Reputation: 5359
Another thing I tried was this:
type AsEnum =
static member Get<'a, 'b when 'a: (new : unit -> 'a) and 'a : struct and 'a :> ValueType and 'a : enum<int>> (text:string) =
match Enum.TryParse<'a>(text) with
| true, value -> Some value
| _ -> None
static member Get<'a, 'b when 'a: (new : unit -> 'a) and 'a : struct and 'a :> ValueType and 'a : enum<int64>> (text:string) =
match Enum.TryParse<'a>(text) with
| true, value -> Some value
| _ -> None
let a = AsEnum.Get<BindingFlags>.Get "DeclaredOnly"
to try to see if I could get the compiler to infer which overload to call, but if fails with an ambiguity error
Upvotes: 0
Reputation: 55184
How about this?
let asEnum s :'a option when 'a:enum<'b> =
match System.Enum.TryParse s with
| true, v -> Some v
| _ -> None
// works, but warns that type params shouldn't be given explicitly
asEnum<System.Reflection.BindingFlags,_> "DeclaredOnly"
// also okay
(asEnum "DeclaredOnly" : System.Reflection.BindingFlags option)
Upvotes: 3