最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Problem with F# inferred generic type constraints - Stack Overflow

programmeradmin2浏览0评论

I have the following F# example. It compiles. However, no matter what I tried, I was unable to make member inline md.mean (x : SubstanceData<^I>) : ^C compile. The compiler immediately tells me that C was constrained to have type double and that was it. Commented out lines in type Model show what I tried (some different combinations of that). I've also tried to use ' instead of ^ here and there but that did not help either.

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)

How to implement this generic md.mean without instantiating types as in Model2D?

I have the following F# example. It compiles. However, no matter what I tried, I was unable to make member inline md.mean (x : SubstanceData<^I>) : ^C compile. The compiler immediately tells me that C was constrained to have type double and that was it. Commented out lines in type Model show what I tried (some different combinations of that). I've also tried to use ' instead of ^ here and there but that did not help either.

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)

How to implement this generic md.mean without instantiating types as in Model2D?

Share Improve this question asked Mar 16 at 16:28 Konstantin KonstantinovKonstantin Konstantinov 1,41513 silver badges22 bronze badges 3
  • In SparseArray.moment you seem to be multiplying 'C by 'V, thus implying that they're the same type. And since in md.mean you're using double for the converter parameter, it means that 'V is double, and therefore so is 'C – Fyodor Soikin Commented Mar 16 at 16:48
  • @FyodorSoikin, if they were implied to have the same type, then getMean2D would not compile because it multiplies Coord2D by a double in the underlying SparseArray.moment. But it does compile and correctly produces a "vector" (Coord2D) result. Thus, there was an idea to specify constraints in the Model so that C could be multiplied by a double (from any side). But that failed. Perhaps some constraints must be explicitly specified in that SparseArray.moment. The question is then which ones? – Konstantin Konstantinov Commented Mar 16 at 18:09
  • Ah, that's because in getMean2D the types are known and are known to have the asymmetric multiplication operator. Hold on, I'll write an answer. – Fyodor Soikin Commented Mar 16 at 21:09
Add a comment  | 

1 Answer 1

Reset to default 2

TL;DR: the multiplication operator is special. There are limits on what you can do with it.


F# treats the multiplication operator in some special ways in order to accommodate the legacy .NET code and libraries. One particular special way is that F# kind of "prefers" it to have parameters of the same type.

You can override this if you have an explicit asymmetric constraint and it's the only one. So, for example, this works:

let inline f<'a, 'b
  when ('a or 'b) : (static member ( * ) : 'a * 'b -> 'a)
>
  (a : 'a) (b: 'b) : 'a = a * b

But as soon as you add another symmetric constraint, it stops working:

let inline f<'a, 'b
  when ('a or 'b) : (static member ( * ) : 'a * 'b -> 'a)
  and 'a : (static member ( * ) : 'a * 'a -> 'a)
>
  (a : 'a) (b: 'b) : 'a = a * b // Type mismatch 'a vs. 'b

In this case the compiler "prefers" the symmetric operator 'a * 'a -> a and ignores the asymmetric one. And since I am passing 'b as second parameter, it's a type mismatch.

And if you try to "force" it by using old-school explicit constraint invocation, you get a warning explaining about the specialness of the operator:

let inline f<'a, 'b
  when ('a or 'b) : (static member ( * ) : 'a * 'b -> 'a)
  and 'a : (static member ( * ) : 'a * 'a -> 'a)
>
  (a : 'a) (b: 'b) : 'a =
    ((^a or ^b) : (static member ( * ) : ^a * ^b -> ^a) a, b)
    // FS77: Member constraints with the name 'op_Multiply' 
    //       are given special status by the F# compiler ...

There is actually quite a bit more nuance to this, but it's too long to write about all of it. What I recommend you do instead is use a different operator for scalar multiplication. That is kind of standard practice in vector libraries anyway.

So, for example, if you use operator *. for scalar multiplication and operator /. for division, this works:

    type SparseArray<'I, 'T
        ...

        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.sumBy (fun v -> (pown (projector v.x) n) *. v.value)

                xn /. x0
            else LanguagePrimitives.GenericZero<'C>

    type Model<'D, 'I, 'C
      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 * ^C -> ^C)
      and (^C or double): (static member ( *. ) : ^C * double -> ^C)
      and (^C or double): (static member ( /. ) : ^C * double -> ^C)
      and ^C: (static member Zero : ^C)
      and ^C: (static member One : ^C)
      > =
        {
            domain : 'D
        }

        member inline md.mean (x : SubstanceData<'I>) : 'C =
            x.substance.mean double (fun (p : ^I) -> p.toCoord md.domain)

    type Model2D = Model<Domain2D, Point2D, Coord2D>

    let getMean2D (md : Model2D) x = md.mean x

发布评论

评论列表(0)

  1. 暂无评论