F#泛型类型推断约束问题:实现通用md.mean成员
问题描述
我编写了以下F#示例代码,可正常编译,但无论尝试何种方案,都无法让member inline md.mean (x : SubstanceData<^I>) : ^C通过编译——编译器直接将类型参数^C约束为double类型。Model类型中被注释的代码是我尝试过的多种约束组合,我也试过将静态解析类型参数^替换为普通泛型参数',但都没解决问题。请告知如何在不实例化Model2D这类具体类型的前提下,实现该通用的md.mean成员?
module TypeTests = type SparseValue<'I, 'T when ^I: equality and ^I: comparison 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 Zero : ^T) and ^T: equality and ^T: comparison> = { x : 'I value : 'T } member inline r.convert converter = { x = r.x; value = converter r.value } type SparseArray<'I, 'T when ^I: equality and ^I: comparison 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 Zero : ^T) and ^T: equality and ^T: comparison> = { values : SparseValue<'I, 'T>[] map : Lazy<Map<'I, 'T>> } static member inline private createLookupMap (values: SparseValue<'I, 'T>[]) = values |> Array.map (fun v -> v.x, v.value) |> Map.ofArray static member inline create v = // Remove all zero values. let values = v |> Array.filter (fun e -> e.value <> LanguagePrimitives.GenericZero<'T>) { values = values map = new Lazy<Map<'I, 'T>>(fun () -> SparseArray.createLookupMap values) } static member inline empty = SparseArray<'I, 'T>.create [||] member inline r.convert converter = r.values |> Array.map (fun v -> v.convert converter) |> SparseArray.create member inline r.moment (converter : 'T -> 'V) (projector : 'I -> 'C ) (n : int) : 'C = let c = r.values |> Array.map (fun v -> v.convert converter) let x0 = c |> Array.sumBy _.value if x0 > LanguagePrimitives.GenericZero<'V> then let xn = c |> Array.map (fun v -> (pown (projector v.x) n) * v.value) |> Array.sum xn / x0 else LanguagePrimitives.GenericZero<'C> member inline r.mean (converter : 'T -> 'V) (projector : 'I -> 'C ) : 'C = let m1 = r.moment converter projector 1 m1 type Domain = { points : double[] } type Domain2D = { d0 : Domain d1 : Domain } type Coord2D = { x0 : double x1 : double } static member Zero = { x0 = 0.0; x1 = 0.0 } static member One = { x0 = 1.0; x1 = 1.0 } static member (+) (a : Coord2D, b : Coord2D) = { x0 = a.x0 + b.x0; x1 = a.x1 + b.x1 } static member (-) (a : Coord2D, b : Coord2D) = { x0 = a.x0 - b.x0; x1 = a.x1 - b.x1 } static member (*) (a : Coord2D, b : Coord2D) = { x0 = a.x0 * b.x0; x1 = a.x1 * b.x1 } static member (*) (d : double, a : Coord2D) = { x0 = d * a.x0; x1 = d * a.x1 } static member (*) (a : Coord2D, d : double) = d * a static member (/) (a : Coord2D, b : Coord2D) = { x0 = a.x0 / b.x0; x1 = a.x1 / b.x1 } static member (/) (a : Coord2D, d : double) = a * (1.0 / d) type Point2D = { i0 : int i1 : int } member p.toCoord (d : Domain2D) = { x0 = d.d0.points[p.i0] x1 = d.d1.points[p.i1] } type SubstanceData<'I when ^I: equality and ^I: comparison> = { substance : SparseArray<'I, int64> // Some more data, which is irrelevant for this example. } type Model<'I, 'C, 'D when ^I: equality and ^I: comparison and ^I: (member toCoord : ^D -> ^C) // and ^C: equality // and ^C: comparison // and ^C: (static member ( + ) : ^C * ^C -> ^C) // and ^C: (static member ( - ) : ^C * ^C -> ^C) // and ^C: (static member ( * ) : ^C * ^C -> ^C) // and ^C: (static member ( * ) : ^C * double -> ^C) // and ^C: (static member ( * ) : double * ^C -> ^C) // and ^C: (static member ( / ) : ^C * double -> ^C) // and ^C: (static member op_Multiply : ^C * double -> ^C) // and ^C: (static member op_Division : ^C * double -> ^C) // and ^C: (static member Zero : ^C) // and ^C: (static member One : ^C) > = { domain : 'D // Some more data, which is irrelevant for this example. } // member inline md.mean (x : SubstanceData<^I>) : ^C = // x.substance.mean double (fun (p : ^I) -> p.toCoord md.domain) type Model2D = Model<Point2D, Coord2D, Domain2D> let getMean2D (md : Model2D) x = x.substance.mean double (fun (p : Point2D) -> p.toCoord md.domain)
解决方案
问题核心是编译器无法从现有约束推导^C所需的运算能力,默认将其绑定到double。需补充^C的静态解析约束,并调整成员调用语法帮助编译器正确推导类型。
1. 完善Model类型的约束
取消Model中注释的^C相关约束,补充^C必须支持的运算和特性:
type Model<'I, 'C, 'D when ^I: equality and ^I: comparison and ^I: (member toCoord : ^D -> ^C) and ^C: equality and ^C: comparison and ^C: (static member ( + ) : ^C * ^C -> ^C) and ^C: (static member ( * ) : ^C * double -> ^C) and ^C: (static member ( / ) : ^C * double -> ^C) and ^C: (static member Zero : ^C) > = { domain : 'D // 其他无关数据 }
2. 修正mean成员实现
使用显式的静态解析成员调用语法,替代.调用,同时明确转换函数的类型:
member inline md.mean (x : SubstanceData<'I>) : ^C = x.substance.mean (fun (v:int64) -> double v) (fun (p:'I) -> (^I : (member toCoord : ^D -> ^C) (p, md.domain)))
完整修改后的Model类型
type Model<'I, 'C, 'D when ^I: equality and ^I: comparison and ^I: (member toCoord : ^D -> ^C) and ^C: equality and ^C: comparison and ^C: (static member ( + ) : ^C * ^C -> ^C) and ^C: (static member ( * ) : ^C * double -> ^C) and ^C: (static member ( / ) : ^C * double -> ^C) and ^C: (static member Zero : ^C) > = { domain : 'D // 其他无关数据 } member inline md.mean (x : SubstanceData<'I>) : ^C = x.substance.mean (fun v -> double v) (fun p -> (^I : (member toCoord : ^D -> ^C) (p, md.domain)))
解决原理
- 补充的约束明确告知编译器
^C支持加法、与double的乘除法及零值,避免默认绑定到double。 - 显式的静态解析成员调用语法,让编译器准确识别
^I的toCoord成员,推导出正确的^C类型。 - 明确的转换函数类型,帮助编译器确定
SparseArray.mean的'V参数为double,配合^C约束完成完整类型推导。
内容的提问来源于stack exchange,提问作者Konstantin Konstantinov
相关产品推荐
相关产品推荐

