You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 21:09:51