4
let inline (=~) a b = abs (single a - single b) <= 0.001f

type Vector =
    { x : single; y : single; z : single }

    static member (=~) (v1, v2) = (v1.x =~ v2.x) && (v1.y =~ v2.y) && (v1.z =~ v2.z)

let v1, v2 =
    { x = 0.1f; y = single Math.PI; z = 0.f },
    { x = 0.1f; y = 3.14159f; z = 0.0001f }

v1 =~ v2

Compiler complains: The type 'Vector' does not support a conversion to the type 'single'

I don't get it. Clearly, the type-specific operator doesn't take precedence over the generic operator, foiling my intuition. What's the trick to making this work?

MiloDC
  • 2,373
  • 1
  • 16
  • 25

2 Answers2

4

When you define a custom operator using let, it will always take precedence over type-defined operators. The simple way of dealing with this is to avoid name clashes in local and global operator names, or to keep the scope of the let-bound operators to a minimum. For example, you could put the global =~ operator in a separate module:

module VectorImplementation = 
  let inline (=~) a b = abs (single a - single b) <= 0.001f

module Vectors = 
  open VectorImplementation
  type Vector =
      { x : single; y : single; z : single }
      static member (=~) (v1, v2) = 
        (v1.x =~ v2.x) && (v1.y =~ v2.y) && (v1.z =~ v2.z)

open System
open Vectors

let v1, v2 =
    { x = 0.1f; y = single Math.PI; z = 0.f },
    { x = 0.1f; y = 3.14159f; z = 0.0001f }

v1 =~ v2

There is also a somewhat strange hack that you can use to define global let-bound operators that are overloaded. There is some debate whether this is a good idea or not - I think it is usually possible to avoid clashes without resorting to this magic trick, but others might disagree.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
2

Page 97 of the F# 4.0 language specification says that:

If the operator does not resolve to a user-defined or library-defined operator, the name resolution rules (§14.1) ensure that the operator resolves to an expression that implicitly uses a static member invocation expression (§0) that involves the types of the operands. This means that the effective behavior of an operator that is not defined in the F# library is to require a static member that has the same name as the operator, on the type of one of the operands of the operator.

As Tomas Petricek has just pointed out in his answer, that means that the =~ operator you defined globally is "hiding" the =~ operator on your Vector type. His answer suggests a good approach for dealing with the problem. Another way would be to simply make both operators different:

let inline (==~) a b = abs (single a - single b) <= 0.001f
type Vector =
    { x : single; y : single; z : single }
    static member (=~) (v1, v2) = 
        (v1.x ==~ v2.x) && (v1.y ==~ v2.y) && (v1.z ==~ v2.z)

You may find this approach simpler than Tomas's approach, or you may find his approach simpler. It's a matter of style preference; either one should work.

rmunn
  • 34,942
  • 10
  • 74
  • 105