bradgonesurfing
bradgonesurfing

Reputation: 32192

How to define operators for discriminated unions in f#

I have the code to implement some geometric operations between primitives

type point = double * double
type shape =
    | Point of point
    | Line of point * point
    | Vector of point
    | Circle of point * double
    with
    member this.ToString = function
        | Point (x,y) -> sprintf "(%f; %f)" x y
        | Vector (x,y) -> sprintf "(%f; %f)" x y
        | Line ((x0,y0),(x1,y1)) -> sprintf "(%f; %f)->(%f; %f)" x0 y0 x1 y1
        | Circle ((x0,y0),radius) -> sprintf "(%f; %f)r%f" x0 y0 radius


let inline (-) (Point (x0,y0)) (Point (x1,y1)) = Vector (x0-x1,y0-y1) 
let inline (+) (Point (x0,y0)) (Vector (x1,y1)) = Point (x0+x1,y0+y1)

And the compiler says that the pattern match on the operators is not exhaustive though this is only a warning. How can I correctly implement operators only between specific sub type of the DU without the compiler complaining?

Upvotes: 3

Views: 842

Answers (4)

Shredderroy
Shredderroy

Reputation: 2920

I would do the following:

type PointT = double * double
type Shape =
    | Point of PointT
    | Line of PointT * PointT
    | Vector of PointT
    | Circle of PointT * double
    with
    member this.ToString = function
        | Point (x,y) -> sprintf "(%f; %f)" x y
        | Vector (x,y) -> sprintf "(%f; %f)" x y
        | Line ((x0,y0),(x1,y1)) -> sprintf "(%f; %f)->(%f; %f)" x0 y0 x1 y1
        | Circle ((x0,y0),radius) -> sprintf "(%f; %f)r%f" x0 y0 radius

let inline (-) (p0 : Shape) (p1 : Shape) : Shape option =
    match p0, p1 with
    | Point(x0, y0), Point(x1, y1) -> Some(Vector(x0 - x1, y0 - y1))
    | _ -> None

let inline (+) (p0 : Shape) (p1 : Shape) : Shape option =
    match p0, p1 with
    | Point(x0, y0), Vector(x1, y1) -> Some(Point(x0 + x1, y0 + y1))
    | _ -> None

Upvotes: 1

John Palmer
John Palmer

Reputation: 25516

The basic problem is that at compile time, the compiler does not know if which specific instance of shape you have chosen to create. As a result, any restriction must be done at run time, or by imposing additional constraints on the type. I think the most elegant solution with run time checking would be something like

type shape = ...
    static member (-) (a,b) = 
        match (a,b) with 
        |Point(c,d),Point(e,f) -> ... 
        |Point(c,d),Vector(e,f) -> ...
        | _ -> failwith "Can't add these shapes"

Alternatively, you could change shape to have point and vector as subtypes of a different DU as follows

type addable = |Point of point |Vector of point

and then modify shape accordingly.

Upvotes: 1

kvb
kvb

Reputation: 55195

As a side note, you've got another problem, which is that ToString should match on this, but right now matches on an anonymous argument (instead of having type unit -> string, it's shape -> string. Also, it should be declared with override, not member (which would also have pointed out that the signature is wrong).

Upvotes: 3

Daniel
Daniel

Reputation: 47914

Operators are typically defined as static members:

type shape =
    ...
    static member (-) (x, y) =
        match x, y with
        | Point (x0,y0), Point (x1,y1) -> Vector (x0-x1,y0-y1) 
        | Point (x0,y0), Vector (x1,y1) -> Point (x0+x1,y0+y1)
        | _ -> failwith "invalid arguments"

A few notes about your attempt:

  1. union cases are not types, so they can't be used to define method overloads
  2. functions can't be overloaded

Upvotes: 4

Related Questions