F# comparison vs C# IComparable
Asked Answered
D

2

8

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 ;;
Datnow answered 6/8, 2013 at 22:53 Comment(2)
It seems odd, int*int implements, IComparable (you can check this with (1,2).GetType().GetInterfaces()), but the compiler won't let you down cast. This may be some sort of weird F# restriction. I imagine that you could write a C# method to do the type cast though.Moderato
I updated my response to be more correct. Sorry for the mistake.Setose
V
1

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)
Viipuri answered 7/8, 2013 at 6:47 Comment(0)
S
8

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 (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 value. You can wrap up the comparable, but you lose type information, the return value will be a IComparable 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.

Setose answered 7/8, 2013 at 3:53 Comment(2)
Thanks for the excellent references. I guess my question isn't 100% clear. I'm interested particularly in the interop between F# and C#, where my C# API requires an IComparable, and I cannot pass raw, structurally-comparable tuples into that API. Does that seem like an implementation deficiency?Datnow
Well, your question is a bit difficult to answer without seeing the C# signature. But if it is a collection, even in C# I would use a constrained generic, because an returning an interface loses some of the type info. What is the signature of the code you are using?Setose
V
1

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)
Viipuri answered 7/8, 2013 at 6:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.