Arithmetic casting to a generic type in F #

I am trying to write a function that performs generic casting for arithmetic types, for example a function that takes an argument of type uint64 and then converts it to a type, the same as a type parameter. My idea:

 let convert<'T> (x:uint64) = 'T x 

But this code does not compile, and I am stuck here after trying several approaches like:

 let convert<'T> (x:uint64) = match Unchecked.defaultof<'T> with | :? uint32 -> uint32 x .... 

So how could I write such a general arithmetic cast in F #? (I'm just starting to learn, so my question may be stupid, please calm down).

+5
source share
3 answers

You can use static member constraints, here is a "short" example:

 type Explicit = static member inline ($) (_:byte , _:Explicit) = byte static member inline ($) (_:sbyte, _:Explicit) = sbyte static member inline ($) (_:int16, _:Explicit) = int16 static member inline ($) (_:int32, _:Explicit) = int // more overloads let inline convert value: 'T = (Unchecked.defaultof<'T> $ Unchecked.defaultof<Explicit>) value // Specialized to uint64 let inline fromUint64 (value: uint64) :'T = convert value // Usage let x:int = fromUint64 7UL 

As stated in the comments, you can use the explicit function from F # + , which covers all cases where there is an explicit operator. Here is an example code .

Now, if you look at the source code for this function, which is defined in another project (FsControl), you will find an even more complicated workaround.

You may wonder why, so here is the long answer:

In theory, it should be possible to use one call that calls the op_Explicit member, but this will only work when that member really exists, which is not the type of native numbers.

In these cases, the F # compiler uses a function, commonly called "simulated elements," which is implemented using static optimizations but is not available outside the source code of the F # compiler.

So F # + uses a different function: overload resolution, as in the code example that I showed you, but it uses extra overload as a general case for those members that really contain the static member op_Explicit .

+2
source

:? type checks allow you to allow (at least this is my understanding) testing subtypes of the type of expression you are matching. Since 'T can be any type, the compiler cannot determine if uint32 subtype of this, so a type test is not possible.

To check for β€œarbitrary” types in matching expressions, you first need the value of box , essentially applying it to obj . Since all other types are subtypes of obj ( Object in C # and the CLR in general), you can then check which types you want.

As you noted correctly, this is not enough, because all branches of the correspondence expression must return the same type. Since only the common supertype of all types of numbers (I know) is obj again, you need to add each conversion again and then lower the result of the match to 'T Theoretically, this is not 100% safe, but in this case you know that the conversion will be performed.

 let convert<'T> (x:uint64) = match box Unchecked.defaultof<'T> with | :? uint32 -> uint32 x |> box | :? int -> int x |> box :?> 'T 

Oh, and it would probably be nice to use something like this in the mission-critical real world (hard loops, etc., lots of calls), since the types of numbers are the types of values ​​allocated on the stack, while each box the number is allocated by the object on the heap that should be garbage collected (iirc, boxing a 4-byte integer creates a 16-byte object, so the difference is quite significant).

+5
source

I'm not saying this is a good idea, but if you want to take the @TeaDrivenDev idea one step further, you can get around the return type restriction using the generic unbox<'T> method.

The performance overhead of all of this can be significant, of course ...

Code example:

 let convert<'T> (x:uint64) : 'T = match box Unchecked.defaultof<'T> with | :? uint32 -> uint32 x |> unbox<'T> | :? uint16 -> uint16 x |> unbox<'T> | :? string -> string x |> unbox<'T> | _ -> failwith "I give up" 1u + (12 |> uint64 |> convert) // val it : uint32 = 13u 1us + (uint64 22 |> convert) // val it : uint16 = 23us 

This restricts the restriction of all branches that must return the same type, since each branch returns the same type for any defined common parameter. The fact that only one branch will ever be returned for a particular parameter exists neither here nor by the compiler.

+5
source

Source: https://habr.com/ru/post/1247669/


All Articles