【问题标题】:Assign a name to a set of type constraints in F#?为 F# 中的一组类型约束分配名称?
【发布时间】:2021-10-17 08:43:13
【问题描述】:

假设我有一个泛型类型,在 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
  }

现在我想创建另一个基于此的泛型类型:

// Does not work

[<Struct>]
type AABB<'t> =
  {
    Min : Vec2<'t>
    Max : Vec2<'t>
  }

除非我复制类型约束,否则这不起作用:

[<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>
  }

这很快就会变老!

有没有办法将类型约束绑定到一个名称,以便我可以在整个代码中重复使用它们?

// 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>
  }

【问题讨论】:

  • 你能解释一下你的用例是什么,即为什么你不能用在你的类型上运行的内联函数来隐藏约束,最好是在一个单独的模块中?否则是一个非常有效的问题!
  • @kaefer 我想定义构成其他类型的新类型。我想将每个记录属性作为参数传递会起作用。但我认为它的可读性会降低。例如,它还可以防止构建组合类型的集合。

标签: f#


【解决方案1】:

在这种情况下,一个合理的解决方法是创建一个表示约束的接口。这不会自动作为命名约束工作,但您可以定义一个辅助函数来捕获所需的操作,然后传递接口(以便您可以调用所需的操作)。

假设我们只需要加法和乘法:

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 }

现在,AABB 类型还包含INumericalOps 接口的实现,它比指定所有约束要短一些。我们可以创建一个内联函数来捕获任何支持 *+ 的类型的实现:

let inline capture () = 
  { new INumericalOps<_> with
    member x.Add(a, b) = a + b
    member x.Mul(a, b) = a * b }

创建值时,类型推断将确保我们得到正确的数值运算实现:

let aabb = 
  { Min = { X = 1.0; Y = 2.0 }
    Max = { X = 1.0; Y = 2.0 }
    Ops = capture() }

【讨论】:

  • 谢谢!从编程的角度来看,这更好,但我认为我看到了两个缺点。 1) 分派到接口会更慢,因为它是在运行时(除非 F# 编译器足够聪明?) 2) AABB 上的比较和相等性将因包含引用类型而被破坏
  • 应该补充一点,包括额外操作的能力,例如 get_PI 可能是支持这种方法的决定性因素。
【解决方案2】:

我相信这本质上就是这个请求https://github.com/fsharp/fslang-suggestions/issues/641

简短的回答,现阶段 F# 不支持,但我认为这将是一个很棒的功能。

【讨论】:

    【解决方案3】:

    您的示例类似于generic math。这个东西将在 net6 中被支持为preview feature。这比所有其他方法都具有优势:运行时原生支持,C# 支持(更多库可用),无运行时成本(性能与直接调用相同)

    即使在 F# 中还没有对此的支持,它可能看起来像这样(不是最终的,语法可能会改变)

    type IAdditionOperators<'a> =
        static abstract (+) : 'a * 'a -> 'a
    
    type IAdditionIdentity<'a> =
        inherit IAdditionOperators<'a>
        static abstract Zero : 'a
    
    /// This vector type supports addition of 2 generic values
    [<Struct>]
    type Vector2<'a when 'a : IAdditionIdentity<'a>>(x, y) =
        member val X = x
        member val Y = y
        static member (+) (left, right) =
            Vector<'a>('a.(+)(left.X, right.X), 'a.(+)(left.Y, right.Y))
    
    /// This vector supports all methods from INumber
    [<Struct>]
    type RichVector2<'a when 'a : INumber<'a>>(x, y) =
        member val X = x
        member val Y = y
        static member (+) (left, right) =
            Vector<'a>('a.(+)(left.X, right.X), 'a.(+)(left.Y, right.Y))
        interface INumber<'a> with
            static member (+) (left, right) =
                 RichVector<'a>.(+)(left, right)
            // Requires implementation for +, -, *, /, DivRem,
            // Zero, One, Abs, Max, Min, Sign, Clamp, 
            // Create, CreateSaturating, CreateTruncating, TryCreate,
            // Parse, TryParse
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-09
      • 2016-11-13
      • 2012-12-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多