4

So far I've been quite impressed with the type inference in F#, however I have found something that it didn't really get:

//First up a simple Vect3 type
type Vect3 = { x:float; y:float; z:float } with 
  static member (/) (v1 : Vect3, s : float) = //divide by scalar, note that float
    {x=v1.x / s; y= v1.y /s; z = v1.z /s}
  static member (-) (v1 : Vect3, v2 : Vect3) = //subtract two Vect3s
    {x=v1.x - v2.x; y= v1.y - v2.y; z=v1.z - v2.z}
  //... other operators...

//this works fine
let floatDiff h (f: float -> float) x = //returns float
  ((f (x + h)) - (f (x - h)))/(h * 2.0)

//as does this
let vectDiff h (f: float -> Vect3) x = //returns Vect3
  ((f (x + h)) - (f (x - h)))/(h * 2.0)


//I'm writing the same code twice so I try and make a generic function:
let genericDiff h (f: float -> 'a) x : 'a = //'a is constrained to a float 
  ((f (x + h)) - (f (x - h)))/(h * 2.0)

When I try and build this last function a blue squiggly appears under the divide sign and the complier says the dreaded warning of "This construct causes code to be less generic than indicated by the type annotations. The type variable 'a has been constrained to be type 'float'". I provide the Vect3 with the suitable / operator for the function. Why is it warning me?

Frank
  • 2,738
  • 19
  • 30
Ed Ayers
  • 1,342
  • 1
  • 11
  • 24

3 Answers3

6

Standard .NET generics are not expressive enough to allow this kind of generic functions. The problem is that your code can work for any 'a that supports the subtraction operator, but .NET generics cannot capture this constraint (they can capture interface constraints, but not member constraints).

However you can use F# inline functions and statically resolved type parameters that can have additional member constraints. I wrote an article that provides some more details about these.

Briefly, if you mark the function as inline and let the compiler infer the type, then you get (I removed explicit mention of the type parameter, as that makes the situation trickier):

> let inline genericDiff h (f: float -> _) x = 
>   ((f (x + h)) - (f (x - h))) / (h * 2.0);;

val inline genericDiff :
  float -> (float ->  ^a) -> float -> float
    when  ^a : (static member ( - ) :  ^a *  ^a -> float)

The compiler now used ^a instead of 'a to say that the parameter is resolved statically (during inlining) and it added a constraint saying that ^a has to have a member - that takes two things and returns float.

Sadly, this is not quite what you want, because your - operator returns Vect3 (and not float as the compiler inferred). I think the problem is that the compiler wants / operator with two arguments of the same type (while yours is Vect3 * float). You can use different operator name (for example, /.):

let inline genericDiff2 h (f: float -> _) x = 
  ((f (x + h)) - (f (x - h))) /. (h * 2.0);;

In this case, it will work on Vect3 (if you rename the scalar division), but it won't easilly work on float (though there may be hacks that will make that possible - see this answer - though I would not consider that idiomatic F# and I'd probably try to find a way to avoid the need for that). Would it make sense to provide elementwise division and pass h as Vect3 value, perhaps?

Community
  • 1
  • 1
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • I think the float literal, `2.0`, is causing the problem. Using generic numbers, the function using `/` works. – Daniel Aug 10 '12 at 14:16
  • @Daniel Interesting. I thought I tried something like that, but the compiler wasn't happy with `^T * float -> ^T`. It definitely works with generic literals (as you did in your answer). – Tomas Petricek Aug 10 '12 at 14:37
  • @Tomas Thanks for the link to your article, I had read about the different annotations on generics before but didn't really get them. The article really helped me make sense of them and why it is useful in numeric code. @Daniel I don't see why the literal should constrain the return type, does the compiler assume that the arg types on `/` are the same? – Ed Ayers Aug 10 '12 at 16:33
  • If one of the types is known (float, in this case), it seems to look for `static member (/)` on that type, whereas, if both types are generic it seems to look at both (what you're expecting). Simple example: `let inline divByTwo x = x / 2.0` constrains `x` to be float, since `float` defines `/` as `float -> float -> float`. It solves the types the only way it possibly can. – Daniel Aug 10 '12 at 18:12
  • I think it's a bug, if inline is specified it should look into both types, if one is still not known then leave it unsolved until both types are known at the call site. I had this issue in the past, will report it as a bug. – Gus Aug 10 '12 at 19:35
  • I agree: while understandable, it doesn't seem quite right. Please let us know what response you get. – Daniel Aug 10 '12 at 19:50
5

If you use generic numbers for your literals, it works:

let inline genericDiff h f x =
  let one = LanguagePrimitives.GenericOne
  let two = one + one
  ((f (x + h)) - (f (x - h))) / (h * two)

genericDiff 1.0 (fun y -> {x=y; y=y; z=y}) 1.0 //{x = 1.0; y = 1.0; z = 1.0;}
Daniel
  • 47,404
  • 11
  • 101
  • 179
  • 1
    Alternatively, replace `h * two` with `h + h` to get rid of two lines – Ed Ayers Aug 10 '12 at 17:01
  • True. This is meant to demonstrate a general solution to the problem. – Daniel Aug 10 '12 at 18:15
  • It seems a weird concept having to build up your bigger constants from lots of ones :) . I suppose once you have two you can start multiplying. So if the constant was 19 you would do: `let sixteen = two * two * two * two; let myConst = sixteen + two + one;` Good fun! – Ed Ayers Aug 12 '12 at 16:52
  • 1
    @Ciemnl Alternatively you can use code like the one presented in this answer http://stackoverflow.com/questions/11562037/function-templates-in-f to avoid having to multiply generic numbers. – Gus Aug 12 '12 at 21:44
1

For some reason the compiler assumes the type signature of division is

^a*^a -> ^b

when it should be okay for it to be

^a*^c -> ^b

I believe this is implied by 9.7 in the spec (this is for units of measure but I don't see why they are a special case). If division could have the desired type signature you could do:

let inline genericDiff h (f: float -> ^a) x = 
    let inline sub a b = (^a: (static member (-):^a * ^a-> ^a) (a,b))
    let inline div a b = (^a: (static member (/):^a -> float-> ^c) (a,b))
    div (sub (f (x+h)) (f(x-h))) (h*2.0)

I have included implicit sub and div but they shouldn't be required - the idea is to make the signature explicit.

I believe the fact that this does not allow ^a to be anything but a float may in fact be a bug.

John Palmer
  • 25,356
  • 3
  • 48
  • 67