6

I am using Microsoft.FSharp.Reflection.FSharpValue.MakeUnion and this requires a Reflection.UnionCaseInfo and an obj[] (that can be empty) as parameters.

However, I am getting a Type mismatch. Expecting a obj [] but given a string [] The type 'obj' does not match the type 'string' when calling with the result of a function that is a string[].

The simplest example I can create of this happening is as follows (I have a test wrapped around this and it doesn't compile because of the line marked !!.

let one (a:obj[]) = a |> Array.map (fun o->printfn "%A" o) |> ignore one [|"a";"b";"c"|] // OK! let str = [|"a";"b";"c"|] //the equivalent of my function return one str//!!Type mismatch. 

I am not sure if I am meant to be casting/converting string[] into an obj[] or ... well, if I am just doing something else wrong that I don't know about.

edit: the actual issue is as described below

let split (by:string) (input:string) = System.Text.RegularExpressions.Regex.Split(input,by) let buildArgs content = match content with | "" -> [||] | _ -> content |> split " " //Type mismatch 

this is what I used to solve: is there a better way?

 | _ -> content |> split " "|> Array.map (fun s->s:>obj)//make sure obj[] is returned 

Casting and Conversions (F#) as reference

I have also tried this

let buildArgs content :obj[] = ... // Type mismatch 

but that also gives me an error:

Type Mismatch on the last line of the function if I don't do the Array.map.

10
  • @phillip-trelford - "you could also change the one function to accept any object array, e.g. let one (a:#obj[])" (deleted along with my 'answer' - reviving as valuable) <- thank you for this suggestion. Sadly I can't use it on FSharpValue.MakeUnion (unless I am mistaken) Commented Aug 18, 2013 at 17:51
  • you could create your own MakeUnion function which calls on to FSharpValue.MakeUnion Commented Aug 18, 2013 at 18:05
  • 1
    Tomas's answer would probably be my default, also you could shorten (fun s -> s:>obj) to box Commented Aug 18, 2013 at 18:11
  • buildArgs as defined does not give me any type mismatch - it returns a string array Commented Aug 18, 2013 at 18:19
  • 2
    @CodeBeard shortest but possibly the most evil workaround for buildArgs is (content |> split " ") |> box |> unbox Commented Aug 18, 2013 at 19:43

2 Answers 2

5

I think your current approach is fine; sometimes I find something like [|for str in ... -> box str|] to be a bit more readable than ... |> Array.map (fun str -> box str) but your mileage may vary. As to why you run into this, there are two somewhat subtle issues here.

As Phil Trelford's comments imply, the .NET type system allows string[] to be treated as obj[] (though doing this from F# requires an upcast and downcast even though the .NET type system isn't so strict). In my opinion this type system "feature" is an abomination and I'd generally avoid it even though it's probably safe in this case (for concurring opinions on the undesirability of array covariance see Covariance and Contravariance in C#, Part Two: Array Covariance and Array covariance: not just ugly, but slow too).

So in general, a string[] won't be treated as an obj[] by the compiler. Why then is everything fine when you pass in [|"a"; "b"; "c"|]? The answer here is that in the specific case of array literals the compiler allows the type of an array expression to be a supertype of each element's type if such a supertype can be inferred (e.g. because it's constrained to obj[] by the signature of another method, as in your case). However, this only works in the case of array literals (that is, expressions of the form [|e1; e2; ... |]).

Sign up to request clarification or add additional context in comments.

7 Comments

That is very clear - thank you. In general, I am feeling 'at the mercy' of the type-system and am having to work around it which is a bit wrong.
I understand. Jut keep in mind that semantically a string array really isn't an object array (even though that might be slightly unintuitive). I'm sure if the designers of .NET could go back in time they'd introduce a ReadOnlyArray type and use it in place of regular arrays in many places throughout the framework (and string ReadOnlyArrays really could be safely treated as object ReadOnlyArrays). Sadly, we have to deal with the legacy we've got, warts and all.
(fun str -> box str) => box ?
@RamonSnir - yes, the ability to use partial application is an advantage of boxing compared to an upcast, though it only works in the case of conversion to obj, of course.
let upcast'<'a> (x : 'a) = x seems to work fine. For example with B : A, [B();B();B­()] |> List.­map upcas­t'<A> is inferred to be of type A list.
|
4

If you define a variable to keep the object array, then you can also use type annotation and F# will upcast the strings automatically to objects:

let values : obj[] = [|"a";"b";"c"|] 

More generally, if the compiler knows the target type before finding the expression (it looks from the left to the right), then it will automatically build an array of objects, even if the literal contains strings. This is also the case of function calls in your question.

However, once you create a value of type string[], you have to convert it to obj[] in some way (as answered by others).

1 Comment

Thank you. I have tried using this notation on the function that returns the array (edited the question to reflect) but that gives me the same exception. I don't have the array as a let it is pipelined out of a pattern match.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.