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?

asked Mar 16 at 16:28
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 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? 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. Commented Mar 16 at 21:09

1 Answer 1

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
answered Mar 16 at 21:54

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.