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 an IComparable
and not something which can be upcast to IComparable
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 that y
is of type 'a
, whereas 'a
can be statically upcast to comparison
(if you want to access a member of comparison
, you will have to upcast to comparison
using :>
first)
On the other hand let x (y : IComparable<int>) = y
requests very explicitly a IComparable<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 to IComparable
. So if you pass (1,2) :> IComparable<int>
or even (1,2) :> _
, the compiler will be able to pass the valueIComparable
and no longer an int*int
.
let wrapComparable value =
{
new IComparable with
member this.CompareTo other =
match other with
| :? 'a as other -> compare value other
| _ -> raise <| InvalidOperationException()
}
Also, here you need to consider, that IComparable
is based on obj
so you probably need to consider the case, where your other
is of a different type.
In case, you only need IComparable<'a>
the code becomes simpler:
let wrapComparable value =
{
new IComparable<_> with
member this.CompareTo other = compare value other
}
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
If you are only using tuples from F# and not exposing them to other languages, and if you are not targeting a version of the .NET Framework that preceded version 4, you can ignore this section.
Tuples are compiled into objects of one of several generic types, all named Tuple, that are overloaded on the arity, or number of type parameters. Tuple types appear in this form when you view them from another language, such as C# or Visual Basic, or when you are using a tool that is not aware of F# constructs. The Tuple types were introduced in .NET Framework 4. If you are targeting an earlier version of the .NET Framework, the compiler uses versions of System.Tuple from the 2.0 version of the F# Core Library. The types in this library are used only for applications that target the 2.0, 3.0, and 3.5 versions of the .NET Framework. Type forwarding is used to ensure binary compatibility between .NET Framework 2.0 and .NET Framework 4 F# components.
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:
let x (y : IComparable) = y
let t = (2, 3)
x (Tuple<_,_> t)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With