Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Casting to and from a type parameter in F#

Tags:

casting

inline

f#

I'm just starting out in F#, and some of the issues around casting are confusing me mightily. Unfortunately, my background reading to try to figure out why is confusing me even more, so I'm looking for some specific answers I can fit into the general explanations...

I've got a ReadOnlyCollection<'T> of enums, produced by this function:

let GetValues<'T when 'T :> Enum> () =
    (new ReadOnlyCollection<'T>(Enum.GetValues (typeof<'T>) :?> 'T[])) :> IList<'T>

What I want to do with it is find all the bits of the enum that are used by its values (i.e., bitwise-or all the values in the list together), and return that as the generic enum type, 'T. The obvious way to do that seemed to me to be this:

let UsedBits<'T when 'T :> Enum> () =
    GetValues<'T>()
    |> Seq.fold (fun acc a -> acc ||| a) 0

...except that that fails to compile, with the error "The declared type parameter 'T' cannot be used here since the type parameter cannot be resolved at compile time."

I can get the actual job done by converting to Int32 first (which I don't really want to do, because I want this function to work on all enums regardless of underlying type), viz.:

let UsedBits<'T when 'T :> Enum> () =
    GetValues<'T>()
    |> Seq.map (fun a -> Convert.ToInt32(a))
    |> Seq.fold (fun acc a -> acc ||| a) 0

...but then the result is produced as Int32. If I try to cast it back to 'T, I again get compilation errors.

I don't want to get too specific in my question because I'm not sure which specifics I should be asking about, so -- where's the flaw(s) in this approach? How should I be going about it?

(Edited to add:, post @Daniel's answer

Alas, this appears to be one of those situations where I don't understand the context well enough to understand the answer, so...

I think I understand what inline and the different constraint are doing in your answer, but being an F# newbie, would you mind awfully expanding on those things a little so I can check that my understanding isn't way off base? Thanks.

)

like image 715
Cerebrate Avatar asked Feb 18 '23 18:02

Cerebrate


1 Answers

You could do this:

let GetValues<'T, 'U when 'T : enum<'U>>() = 
  Enum.GetValues(typeof<'T>) :?> 'T[]

let inline GetUsedBits() =
  GetValues() |> Seq.reduce (|||)

inline allows a more flexible constraint, namely 'T (requires member ( ||| )). Without it, the compiler must choose a constraint that can be expressed in IL, or, if unable to do so, choose a concrete type. In this case it chooses int since it supports (|||).

Here's a simpler repro:

let Or a b = a ||| b //add 'inline' to compare

See Statically Resolved Type Parameters on MSDN for more info.

like image 75
Daniel Avatar answered Apr 02 '23 05:04

Daniel