Suppose I have a generic type with some complex type constraints in F#:
[<Struct>]
type Vec2<'t when 't : equality
and 't : comparison
and 't : (static member get_Zero : Unit -> 't)
and 't : (static member (+) : 't * 't -> 't)
and 't : (static member (-) : 't * 't -> 't)
and 't : (static member (*) : 't * 't -> 't)
and 't : (static member (/) : 't * 't -> 't)> =
{
X : 't
Y : 't
}
Now I want to create another generic type that builds on this:
// Does not work
[<Struct>]
type AABB<'t> =
{
Min : Vec2<'t>
Max : Vec2<'t>
}
This does not work unless I replicate the type constraints:
[<Struct>]
type AABB<'t when 't : equality
and 't : comparison
and 't : (static member get_Zero : Unit -> 't)
and 't : (static member (+) : 't * 't -> 't)
and 't : (static member (-) : 't * 't -> 't)
and 't : (static member (*) : 't * 't -> 't)
and 't : (static member (/) : 't * 't -> 't)> =
{
Min : Vec2<'t>
Max : Vec2<'t>
}
This gets old fast!
Is there a way to bind the type constraints to a name so that I can reuse them throughout my code?
// Not real code
constraint IsNumeric 't =
't : equality
and 't : comparison
and 't : (static member get_Zero : Unit -> 't)
and 't : (static member (+) : 't * 't -> 't)
and 't : (static member (-) : 't * 't -> 't)
and 't : (static member (*) : 't * 't -> 't)
and 't : (static member (/) : 't * 't -> 't)
[<Struct>]
type Vec2<'t when IsNumeric 't> =
{
X : 't
Y : 't
}
[<Struct>]
type AABB<'t when IsNumeric 't> =
{
Min : Vec2<'t>
Max : Vec2<'t>
}
A reasonable workaround in this case is to create an interface that represents the constraints. This does not automatically work as named constraint, but you can define a helper function that captures the required operations and then pass the interface around (so that you can invoke the operations you want).
Let's say we want just addition and multiplication:
type INumericalOps<'T> =
abstract Add : 'T * 'T -> 'T
abstract Mul : 'T * 'T -> 'T
[<Struct>]
type Vec2<'T> =
{ X : 'T
Y : 'T }
[<Struct>]
type AABB<'T, 'O when 'O :> INumericalOps<'T>> =
{ Min : Vec2<'T>
Max : Vec2<'T>
Ops : 'O }
Now, the AABB
type also contains an implementation of the INumericalOps
interface, which is somewhat shorter than specifying all the constraints. We can create an inline function that captures the implementation of *
and +
for any type that supports those:
let inline capture () =
{ new INumericalOps<_> with
member x.Add(a, b) = a + b
member x.Mul(a, b) = a * b }
When creating a value, the type inference will make sure we get the right implementation of numerical operations:
let aabb =
{ Min = { X = 1.0; Y = 2.0 }
Max = { X = 1.0; Y = 2.0 }
Ops = capture() }
I believe this is essentially this request https://github.com/fsharp/fslang-suggestions/issues/641
Short answer, not supported in F# at this stage but I think it would be a great feature to add.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With