F # General Methods and Operators

So far, I have been very impressed with type inferences in F #, however I found something that didn't work out:

//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 to build this last function, a blue squiggly appears under the division sign, and the compiler says that the scary warning is β€œThis construct causes the code to be less general than indicated by type annotations. The type of the variable a is limited to the typeβ€œ float. ”I provide Vect3 with the appropriate / operator for function. Why does this warn me?

+4
source share
3 answers

Standard .NET generics are not expressive enough to allow such common functions. The problem is that your code can work for anyone that supports the subtraction operator, but .NET generics cannot capture this restriction (they can capture interface restrictions, but not member restrictions).

However, you can use the F # inline functions and statically permitted type parameters , which may have additional restrictions for members. I wrote an article that contains more detailed information about this.

In short, if you mark the function as inline and let the compiler infer the type, then you will get (I removed the explicit mention of the type parameter, as this makes the situation more complicated):

 > 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) 

Now the compiler used ^a instead of 'a to say that the parameter was resolved statically (during attachment), and he added a constraint that said ^a must have a member - which takes two things and returns a float .

Unfortunately, this is not exactly what you want, because your operator returns Vect3 (and not float as an output compiler). I think the problem is that the compiler wants / operator with two arguments of the same type (while your Vect3 * float ). You can use another 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 scalar division), but it will not work on float easily (although there may be hacks that make this possible - see this answer - although I would not think about this idiomatic F # , and I will probably try to find a way to avoid the need for this). It would be advisable to provide elementary division and pass h as a Vect3 value, perhaps?

+6
source

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

 let inline genericDiff hfx = 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;} 
+5
source

For some reason, the compiler assumes that the signature is a division type

 ^a*^a -> ^b 

when for him should be

 ^a*^c -> ^b 

I believe this is meant by 9.7 in spec (this is for units, but I don’t understand why they are a special case). If the division can have the type signature you want, you can do:

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

I have included implicit sub and div , but they should not be required - the idea is to make the signature explicit.

I believe that the fact that this doesn’t allow ^a be anything but a float may actually be a mistake.

+1
source

All Articles