1

I need to call a function that takes System.Array [] as one parameter in F#. (The function is in a library). I need to pass an argument of type float [] [] [] but the compiler refuses to compile. To replicate the problem, I wrote the following code

let x : float [] [] = Array.init 2 (fun x -> Array.zeroCreate 3)
x :> System.Array;; // This is OK

val x : float [] [] = [|[|0.0; 0.0; 0.0|]; [|0.0; 0.0; 0.0|]|]

> x :> System.Array [];; //Error

  x :> System.Array [];;
  ^^^^^^^^^^^^^^^^^^^^

stdin(14,1): warning FS0059: The type 'System.Array []' does not have any proper subtypes and need not be used as the target of a static coercion

  x :> System.Array [];;
  ^^^^^^^^^^^^^^^^^^^^

stdin(14,1): error FS0193: Type constraint mismatch. The type 
    float [] []    
is not compatible with type
    System.Array []    
The type 'System.Array' does not match the type 'float []'

How can I solve this problem?

Thanks in advance.

LLS
  • 2,128
  • 2
  • 23
  • 36

2 Answers2

4

The ability to treat an 's[] as a 't[] when 's :> 't makes the .NET type system unsound (and is probably due to the fact that Java does the same thing). Unfortunately, C# follows .NET in allowing this.

Because it's a .NET runtime feature, you can also do it in F# via boxing and unboxing:

let x : float[][] = Array.init 2 (fun x -> Array.zeroCreate 3)
let x' = (box x) :?> System.Array[]

This avoids the overhead of mapping each element as in Ramon's solution.

To see why this makes the .NET type system unsound, consider this:

x'.[0] <- upcast [| "test" |] // System.ArrayTypeMismatchException

Even though we are storing a value of type System.Array into a System.Array[], we get an exception at runtime because the true type of the underlying array can't support the operation (x and x' are just two views of the same array, and obviously a string[] can't be stored into x). This unsoundness in the .NET type system therefore has the undesirable side effect of requiring additional overhead for most stores into arrays to ensure that the stored value has a type compatible with the underlying array. In my opinion, it's a good thing that F# prevents you from doing this directly.

kvb
  • 54,864
  • 2
  • 91
  • 133
  • Thanks for pointing out this problem. I was actually confused by this behavior when I first learned that I could do this in both Java and C#, and it turns out that array type is covariant in both languages. It turns out that F# just chooses to return to the normal sense of inheritance. – LLS Oct 28 '11 at 06:19
2

You could do this:

let x : float [] [] = Array.init 2 (fun x -> Array.zeroCreate 3)

let toArray (xs : #System.Array []) =
    Array.map (fun x -> x :> System.Array) xs

let x' : System.Array [] = toArray x
Ramon Snir
  • 7,520
  • 3
  • 43
  • 61