Operator Overloading

Operator overloading is a feature in F# which allows you to overload an arithmetic operator or define a custom operator in a class or record type.

To overload an operator as a class or record member, add static member to the type definition as follows:

static member (+) (a : Point, b: Point) = 
    // method body

How to implement?

> type Point = 
    {
        x: int
        y: int
    } with
    static member (+) (a: Point, b: Point) = [a; b]
    static member (+) (a: Point, b: Point list) = b @ [a]
    static member (+) (a: Point list, b: Point) = 
        // reusing operator overload#2 above
        b + a
;;
type Point =
  {
    x: int
    y: int
  }
  static member (+) : a: Point list * b: Point -> Point list + 2 overloads

> let a = {x = 2; y = 3} ;;
val a: Point = { x = 2
                 y = 3 }

> let b = {x = 8; y = 10} ;;  
val b: Point = { x = 8
                 y = 10 }

> let c = a + b ;;
val c: Point list = [{ x = 2
                       y = 3 }; { x = 8
                                  y = 10 }]

> c + a ;;
val it: Point list = [{ x = 2
                        y = 3 }; { x = 8
                                   y = 10 }; { x = 2
                                               y = 3 }]

> a + c ;;
val it: Point list = [{ x = 2
                        y = 3 }; { x = 8
                                   y = 10 }; { x = 8
                                               y = 10 }]

Infix operator

Can we do operator overload for existing types?

No.

You can try it by extending existing type but the compiler will show a message saying Extension members cannot provide operator overloads:

> type Point = 
    {
        x: int
        y: int
    }
;;

> type Point with
    static member (+) (a: Point, b: Point) = 
        {x = a.x + b.x; y = a.y + b.y}
;;

      static member (+) (a: Point, b: Point) =
  -------------------^

stdin(238,20): warning FS1215: Extension members cannot provide operator overloads.  Consider defining the operator as part of the type definition instead.

type Point with
  static member (+) : a: Point * b: Point -> Point

One workaround for this is to create a wrapper type around the existing type:

> type PointExt (p: Point) = 
    member this.point = p
    static member (+) (a: PointExt, b: PointExt) = 
        PointExt({ 
            x = a.point.x + b.point.x
            y = a.point.y + b.point.y
        })
;;
type PointExt =
  new: p: Point -> PointExt
  static member (+) : a: PointExt * b: PointExt -> PointExt
  member point: Point

> let a = PointExt({x = 2; y = 3}) ;;
val a: PointExt

> let b = PointExt({x  = 10; y = 12}) ;;
val b: PointExt

> a + b ;;
val it: PointExt = FSI_0093+PointExt {point = { x = 12
                                                y = 15 };}

Define Custom Operator

> type Point = 
    {
        x: int
        y: int
    }
    static member (+@) (a: Point, b: Point) = 
        { x = a.x + b.x; y = a.y + b.y }
;;
type Point =
  {
    x: int
    y: int
  }
  static member (+@) : a: Point * b: Point -> Point

> let a = {x = 2; y = 3} ;;
val a: Point = { x = 2
                 y = 3 }

> let b = {x = 8; y = 10} ;;
val b: Point = { x = 8
                 y = 10 }

> a +@ b ;;
val it: Point = { x = 10
                  y = 13 }

Define Unary Operator

> type Point = 
    {
        x: int
        y: int
    }
    static member (!!) (p: Point) = 
        { x = p.x * -1; y = p.y * -1 }
    static member (~~) (p: Point) = 
        { x = p.x * -1; y = p.y * -1 }
;;
type Point =
  {
    x: int
    y: int
  }
  static member (!!) : p: Point -> Point
  static member (~~) : p: Point -> Point

> let point = { x = 10; y = 20 } ;;
val point: Point = { x = 10
                     y = 20 }

> !! point ;;
val it: Point = { x = -10
                  y = -20 }

> ~~ point ;;
val it: Point = { x = -10
                  y = -20 }

Overloaded Operator Names

> type Point = 
    {
        x: int
        y: int
    }
    static member op_Addition (a: Point, b: Point) = 
        { x = a.x + b.x; y = a.y + b.y }
;;
type Point =
  {
    x: int
    y: int
  }
  static member (+) : a: Point * b: Point -> Point

For full list of standard operator names, check out table A.

Names for custom operator

Operators at the Global Level

You can define operators at global level using let keyword:

> let inline (^+*+^) (x: int) (y: int) = x*x + 2*x*y + y*y ;; 
val inline (^+*+^) : x: int -> y: int -> int

> 5 ^+*+^ 9 ;;
val it: int = 196
> let inline (+@) x y = x + x * y ;;
val inline (+@) :
  x:  ^a -> y:  ^c ->  ^d
    when ( ^a or  ^b) : (static member (+) :  ^a *  ^b ->  ^d) and
         ( ^a or  ^c) : (static member ( * ) :  ^a *  ^c ->  ^b)

> printfn "%d" (1 +@ 1) ;;
2
val it: unit = ()

> printfn "%f" (1.0 +@ 0.5) ;;
1.500000
val it: unit = ()

Tables

Table A

Generated names for standard operators

OperatorGenerated name
[]op_Nil
::op_Cons
+op_Addition
-op_Subtraction
*op_Multiply
/op_Division
@op_Append
^op_Concatenate
%op_Modulus
&&&op_BitwiseAnd
|||op_BitwiseOr
^^^op_ExclusiveOr
<<<op_LeftShift
~~~op_LogicalNot
>>>op_RightShift
~+op_UnaryPlus
~-op_UnaryNegation
=op_Equality
<=op_LessThanOrEqual
>=op_GreaterThanOrEqual
<op_LessThan
>op_GreaterThan
?op_Dynamic
?<-op_DynamicAssignment
|>op_PipeRight
<|op_PipeLeft
!op_Dereference
>>op_ComposeRight
<<op_ComposeLeft
<@ @> *op_Quotation
<@@ @@> *op_QuotationUntyped
+=op_AdditionAssignment
-=op_SubtractionAssignment
*=op_MultiplyAssignment
/=op_DivisionAssignment
..op_Range
.. ..op_RangeStep

* Quotation symbols can't be used as an operator, but for some reason F# decided to give a special name for it. Relevant StackOverflow question.

Table B

Names of symbols allowed as operator characters:

Operator characterName
>Greater
<Less
+Plus
-Minus
*Multiply
/Divide
=Equals
~Twiddle
$Dollar
%Percent
.Dot
&Amp
|Bar
@At
^Hat
!Bang
?Qmark
(LParen
,Comma
)RParen
[LBrack
]RBrack

© 2022 Sumeet Das