Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 570
BitTickler Avatar asked Jan 18 '26 16:01

BitTickler


2 Answers

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.

like image 93
Gus Avatar answered Jan 21 '26 08:01

Gus


This makes it generic:

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

But there's no way to constrain it to signed primitive types.

like image 22
Daniel Avatar answered Jan 21 '26 09:01

Daniel



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!