Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assign a name to a set of type constraints in F#?

Tags:

f#

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>
  }
like image 889
sdgfsdh Avatar asked Oct 17 '21 08:10

sdgfsdh


2 Answers

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() }
like image 75
Tomas Petricek Avatar answered Oct 23 '22 11:10

Tomas Petricek


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.

like image 29
tranquillity Avatar answered Oct 23 '22 10:10

tranquillity