My problem, in a nutshell, is this:
What can I do about storing a tuple (or any type with a constraint of 'comparison') in a C# container that requires an IComparable?
This works:
> let x (y : 'a when 'a : comparison) = y ;;
val x : y:'a -> 'a when 'a : comparison
> x (1,2) ;;
val it : int * int = (1, 2)
I would have thought this would work:
> let x (y : IComparable<int>) = y ;;
val x : y:IComparable<int> -> IComparable<int>
> x (1,2) ;;
x (1,2) ;;
---^^^
stdin(28,4): error FS0001: The type ''a * 'b' is not compatible with the type 'IComparable<int>'
And this as well:
> let x (y : IComparable) = y ;;
val x : y:IComparable -> IComparable
> x (1,2) ;;
x (1,2) ;;
---^^^
stdin(30,4): error FS0001: The type ''a * 'b' is not compatible with the type 'IComparable'
EDIT
I follow the argument that F# doesn't do implicit upcasting. However, even explicitly:
> (1, 2) :> IComparable ;;
(1, 2) :> IComparable ;;
^^^^^^^^^^^^^^^^^^^^^
stdin(43,1): error FS0193: Type constraint mismatch. The type
int * int
is not compatible with type
IComparable
The type 'int * int' is not compatible with the type 'IComparable'
I suppose this makes sense as the comparability of a F# tuple is inferred structurally within the F# type system, and perhaps that extra information is not available to .NET.
It seems one workaround per a comment below is invoking
Tuple<_,_> (1,2) ;;
Or even
box (1, 2) :?> IComparable ;;
F# does not do implicit upcasting as C# does. If you request an
IComparable
, then you are requesting anIComparable
and not something which can be upcast toIComparable
What you really want, is requesting a type, that happens to implement
IComparable
, but you are still working with the specific type.That's why
let x (y : 'a when 'a : comparison)
, see thaty
is of type'a
, whereas'a
can be statically upcast tocomparison
(if you want to access a member ofcomparison
, you will have to upcast tocomparison
using:>
first)On the other hand
let x (y : IComparable<int>) = y
requests very explicitly aIComparable<int>
.But you are passing. You can wrap up the comparable, but you lose type information, the return value will be a(1,2)
, a value, that can be upcast toIComparable
. So if you pass(1,2) :> IComparable<int>
or even(1,2) :> _
, the compiler will be able to pass the valueIComparable
and no longer anint*int
.Also, here you need to consider, that
IComparable
is based onobj
so you probably need to consider the case, where yourother
is of a different type.In case, you only need
IComparable<'a>
the code becomes simpler:As such, as a rule of thumb, you usually want to make a generic function with type constraints, rather than requesting an interface, as you would in C#. This is due to the fact, that F# does not do automatic upcasting.
A very detailed explanation about equality and comparisons can be found in http://lorgonblog.wordpress.com/2009/11/08/motivating-f-equality-and-comparison-constraints/ and http://blogs.msdn.com/b/dsyme/archive/2009/11/08/equality-and-comparison-constraints-in-f-1-9-7.aspx. Also the MSDN states, that
So it seems, that the fact, that Tuples, happen to be System.Tuple is really just an implementation detail at which point, the lack of
IComparison
makes somewhat sense.Definitely some weirdness going on. FWIW, it works if you construct a
System.Tuple<_, _>
explicitly, so that might be a workaround: