Problems with referencing C# defined custom operator from F#

Dear community,

I have a problem and my preferred solution does not work:

  1. Problem:
    There is a C# library with an overloaded == operator gurobi 9.1. I want to use this operator in F#, such as in
  let setEqual (lhs : GRBLinExpr, rhs : GRBLinExpr) =
      GRBLinExpr.(==) (lhs, rhs)

This fails with “FS0039 The type ‘GRBLinExpr’ does not define the field, constructor or member ‘==’.”

Also addressing it as member op_EqualsEquals as outlined in F# ops does lead to the same error.

  1. Extra information: Adding the library to a C# project shows me:
public static Gurobi.GRBTempConstr operator ==(Gurobi.GRBLinExpr lhs, Gurobi.GRBLinExpr rhs)
    Member of Gurobi.GRBLinExpr
)

and the following compiles

static GRBTempConstr SetEqual(GRBLinExpr lhs, GRBLinExpr rhs)
    {
      return lhs == rhs;
    }
  1. Possible workaround: I can create an extra C# project, wrap the == operator there in a static method and reuse this method in F#. Pro: Works. Cons: extra project.

So my questions are:

a) Why does F# not allow me to use this operator? Are there some limitations to using operators from F#? The F# docs does not mention any (sorry, see “F# ops” link above, as this forum does not let me post the same link twice :-() .

b) Are there other workarounds than 3)? Maybe with reflection?

Cordially,
htc

I think you can do it with GRBLinExpr.op_Equality. The table in that F# ops link of yours lists that for the = operator and not ==, but for C# code, I think it uses == everywhere that F# uses =.
At least in my testing, I can have a C# class:

public class Class1
{
  private int A;
  public Class1(int a) => A = a;
  public static bool operator ==(Class1 a, Class1 b) => a.A == b.A;
  public static bool operator !=(Class1 a, Class1 b) => a.A != b.A;
}

and then from F# I can do

printfn "%A" (Class1.op_Equality (Class1 6, Class1 6))

I just downloaded that library you mentioned and checked the documentation from the URL you gave and keep in mind that the equality operator here does not return a boolean. In fact, it is a GRBTempConstr. In other words, if you try to use this in a context where equality is expected, it won’t work.

a) Why does F# not allow me to use this operator?

As @ntwilson already showed, it is not the op_EqualsEquals, but the op_Equality that you are after. C# is confusing here: when C# uses ==, it calls the Equals overload, or the op_Equality, if it is defined, not the op_EqualsEquals

Are there some limitations to using operators from F#? The F# docs does not mention any

No. So, even though the result here is not a boolean, you can use it.


It is important to realize that when you use the = (equality) operator in F#, it will do structural comparison. In other words, even if you have the op_Equality defined, this will not be called automatically.

If we slightly adjust your code, it compiles:

let setEqual (lhs: GRBLinExpr, rhs: GRBLinExpr) = 
    GRBLinExpr.(=) (lhs, rhs)

Or:

let setEqual (lhs: GRBLinExpr, rhs: GRBLinExpr) = 
    GRBLinExpr.op_Equality (lhs, rhs)

As you can see, you needed op_Equality here (which is what operator (==) compiles to from C#) and not op_Equals (which, btw, is not actually listed in that F# Operator list that you linked).

If we look at the compiled MSIL code in ILSpy, we see the same. The actual operator (==) from C# is compiled into a static member op_Equality and not op_Equals.

However, you are right in that, to map the F# operator == (double-equals), you need to define an operator op_EqualsEquals, which you can then call the same way you do for C#, if you would like that (that is, as lhs == rhs).

Thanks @ntwilson! I so rarely write C# code, and fixated on translating == into F# as ==, not =. Of course I found nothing. Thanks for unblocking me!

@abelbraaksma. Thank you for the pointer to using custom = when using F# native types (where structural comparison is automatically implemented by default). That is not the case here, but nice to remember for the next internal DSL I write.