Question

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?

Was it helpful?

Solution

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

OTHER TIPS

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).

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.

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top