F# comparison vs C# IComparable

后端 未结 2 1291
遇见更好的自我
遇见更好的自我 2021-01-12 14:35

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 ICom

相关标签:
2条回答
  • 2021-01-12 14:56

    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)
    
    0 讨论(0)
  • 2021-01-12 14:57

    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.

    0 讨论(0)
提交回复
热议问题