Mikeb
Mikeb

Reputation: 791

F# Custom Comparison operators for type

I want to create custom comparison operators for my type (<, >, <=, >=, etc). I have tried:

type MyType() =
    static member (>) (left: MyType, right: int) = true


let foo = new MyType();
let bar = foo > 12;

and receive the error:

The type 'MyType' does not support the 'comparison' constraint. For example, it does not support the 'System.IComparable' interface

(Why would a language feature like operator overloading depend on an interface IComparable that comes from a framework? shouldn't the language and the frameworks it use be independent?) So I try:

type MyType() =
    interface IComparable
        member self.CompareTo yobj = true
    interface IComparable<int>
        member self.CompareTo yobj = true
    static member (>) (left: MyType, right: int) = true

and I get:

This expression was expected to have type 'MyType' but here has type 'int'

How can I get the expected behavior?

Upvotes: 3

Views: 1507

Answers (2)

itminus
itminus

Reputation: 25350

The Reason Why You Get Such An Error

I don't know why F# doesn't allow me to write such codes like C#.

public class MyType  
{
    public int Value {get;set;}

    public static bool operator >(MyType left, int right) 
    {
        return left.Value > right; 
    }

    public static bool operator <(MyType left, int right) 
    {
        return left.Value < right; 
    }
}

Even if this type doesn't implement the IComparable interface, I could compare it with an int as below:

t > 2
t < 6

It seems that F# treat the (>) as a T' -> T' -> bool:

val ( > ): 
   x: 'T (requires comparison )->
   y: 'T (requires comparison )
   -> bool

This indicates :

  1. The left and right parameters are the same Type.
  2. The T' requires comparison (IComparable)

If I understand it correctly, this is the reason why you get an error like:

This expression was expected to have type 'MyType' but here has type 'int'

Even if you has implemented the IComparable interface, the standard (>) requires that the left and the right parameters belong to the same Type.

A Walkaround

One walk around is to create a custom function (>) that accepts a left MyType and a right int parameters directly:

type MyType(info)  = 
    member x.Info : int = info 


let inline (>) (left: MyType) (right: int) = 
    left.Info > right

let inline (<) (left: MyType) (right: int) = 
    left.Info < right


// see https://stackoverflow.com/questions/19682432/global-operator-overloading-in-f
let inline (>) (left) (right) = 
    match (box left, box right) with
    | (:? MyType as l, :? int as r ) ->
        l.Info > int right
    | (:? IComparable as left', :? IComparable  )->
        let r = left'.CompareTo right
        r > 0
    | _ -> failwith "not support type "

let inline (<) (left) (right) = 
    match (box left, box right) with
    | (:? MyType as l, :? int as r ) ->
        l.Info < int right
    | (:? IComparable as left', :? IComparable  )->
        let r = left'.CompareTo right
        r < 0
    | _ -> failwith "not support type "

Upvotes: 4

Phillip Carter
Phillip Carter

Reputation: 5005

Note that there is also a warning emitted at the declaration of >:

The name '(>)' should not be used as a member name. To define comparison semantics for a type, implement the 'System.IComparable' interface. If defining a static member for use from other CLI languages then use the name 'op_GreaterThan' instead.

As itminus' answer shows, this is because > is defined on two 'Ts that satisfy the comparable constraint.

There is no operator overloading in F# like you can do in C#. This is because operators are functions, and functions cannot be overloaded.

So your choices are either to (a) implement System.IComparable as the warning suggests, or (b) apply a workaround with inline functions as itminus proposes.

Upvotes: 2

Related Questions