Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unbox F# tuple object where tuple has arbitrary number of elements

Tags:

f#

Converting list to tuple using how-can-i-convert-between-f-list-and-f-tuple

Adding let a' = Array.map box a since MakeTuple expects obj array, and let t = unbox<float*float*float> o to get the tuple.

Example:

let a = List.toArray [1.0; 2.0; 3.0]
let types = a |> Array.map (fun o -> o.GetType())
let tupleType = Microsoft.FSharp.Reflection.FSharpType.MakeTupleType types
let a' = Array.map box a
let o = Reflection.FSharpValue.MakeTuple (a' , tupleType)
let t = unbox<float*float*float> o

This runs for float list with length 3. Returns val t : float * float * float = (1.0, 2.0, 3.0) as desired.

But I want to convert lists of arbitrary length.

Converting from list to obj works for arbitrary length (and type), my problem is the unboxing.

like image 559
holmen Avatar asked Jan 20 '26 20:01

holmen


1 Answers

You motivate the question using a somewhat arbitrary example. I assume that your actual motivation for wanting this is something more tricky and it would be good to know what that actual motivation is - because it might be useful in giving a good answer.

First, if you want to work with a tuples of arbitrary length, you should probably not be using tuples. F# tuples are fixed (statically known) length. Other uses of them will be cumbersome. (Unlike in some dynamic languages where tuples are used for arbitrary-lenght data more frequently.) Second, if you do not know the type at compile-time, I'm not sure what you want to achieve via unboxing.

Depedning on your actual motivation, there is one trick that may be helpful. The trick is to invoke a generic method via reflection, passing it the unboxed values.

If we assume that all elements of your tuple have the same type, you can take them as an array (of unboxed, but generic values):

type Helper = 
  static member Process<'T>(data:'T[]) = 
    typeof<'T>.Name

Now you can invoke Helper.Process using reflection:

open Microsoft.FSharp.Reflection

let a = (1., 2.)

let tys = FSharpType.GetTupleElements(a.GetType())
for ty in tys do if ty <> tys.[0] then failwith "All types must be the same"

let flds = FSharpValue.GetTupleFields(a)
let arr = System.Array.CreateInstance(tys.[0], flds.Length)
System.Array.Copy(flds, arr, flds.Length)
typeof<Helper>.GetMethod("Process")
  .MakeGenericMethod([| tys.[0] |]).Invoke(null, [| arr |] )

This returns "Double", because when called via reflection, the type of 'T will be Double, so you are actually getting the values as unboxed.

There are some rare cases where a thing like this is useful, but my guess is that you do not actually need this and, instead, you'd be probably better off with a different way of representing your data.

like image 184
Tomas Petricek Avatar answered Jan 23 '26 20:01

Tomas Petricek