Search code examples
f#inlinetype-constraints

How to use f# constraints for generic calculation functions?


type Point<'t> =
    val X : 't
    val Y : 't
    new(x : 't,y : 't) = { X = x; Y = y }

let clampedSubtract (p1:Point<_>) (p2:Point<_>) =
    Point( max (p2.X - p1.X) 0, max (p2.Y - p1.Y) 0 )

If you look at the code above, you will notice, that the function is not implemented as generic as it should be.

First, using the 0 in the max expressions clamps the type to int. But it should be the type of whatever type Point<'t> has and not Point<int>.

But even more important, this function can only work as expected, if signed types are used for `t.

This raises a few questions of mine:

  1. Is there a way to obtain the neutral element (zero) from a generic (number) type?
  2. How can I express a restriction such as "only signed number"?
  3. Is there a way to extend type constraint system in F#?

Thanks, in advance.


Solution

  • The solution to the first question as already answered is to use an inline function together with GenericZero and that's all.

    Regarding the signed restriction, actually there's an easy way to restrict it to signed types. Use somewhere the generic unary negation which is defined only for signed types:

    let inline clampedSubtract (p1:Point<_>) (p2:Point<_>) =
        let zero = LanguagePrimitives.GenericZero
        Point( max (p2.X + -p1.X) zero, max (p2.Y + -p1.Y) zero )
    
    let result1 = clampedSubtract (Point(4  , 5  )) (Point(4  , 5  ))
    let result2 = clampedSubtract (Point(4y , 5y )) (Point(4y , 5y ))
    let result3 = clampedSubtract (Point(4uy, 5uy)) (Point(4uy, 5uy)) // doesn't compile
    

    In general, if you want to restrict any generic function to signed types you can define this function:

    let inline whenSigned x = ignore (-x)
    
    let inline clampedSubtract (p1:Point<_>) (p2:Point<_>) =
        whenSigned p1.X
        let zero = LanguagePrimitives.GenericZero
        Point( max (p2.X - p1.X) zero, max (p2.Y - p1.Y) zero )
    

    Finally regarding your third question it's not very clear to me what do you mean with extending the type system. You can create static constraints by yourself, in that sense the system is already extensible.

    I did a project sometime ago to emulate some Haskell types, part of the code of that project is still in a module in FsControl there you can have an idea to what level you can play with those constraints.