stmax
stmax

Reputation: 6605

F# type inference and somewhat generic function parameters

I have a function like the following:

let inline fmingd f' x0 alpha =
  let rec step x i =
    if i >= 100 then x
    else step (x - alpha*(f' x)) (i+1)
  step x0 0

It's generic enough to work with floats:

let f' x = 4.*x + 3.
let fminx = fmingd f' 0. 0.1

... and with (MathNet.Numeric) vectors:

let f' (x:Vector<float>) = (x.[0], x.[1]) |> fun (x, y) -> vector [4.*x + 3.; 8.*y + 5.]
let fminx = fmingd f' (vector [0.;0.]) 0.1

... or with anything else that supports the right operators ( * and - ).

What I'd like to do is to restrict the parameter alpha to float (while keeping the other parameters generic):

let inline fmingd f' x0 (alpha:float) = ...

That should work since vectors have operator ( * ) with float -> vector -> vector and operator ( - ) with vector -> vector -> vector.

But as soon as I do that the function becomes non-generic.. it only accepts and returns floats. Its type signature becomes:

val inline fmingd : f':(float -> float) -> x0:float -> alpha:float -> float

What can I do to keep f', x0 and fmingds return value generic while restricting alpha to float?

I tried to impress the F# compiler by coming up with the following:

let inline fmingd (f':^a -> ^a) (x0:^a) (alpha:float) : ^a
    when ^a : (static member ( * ) : float * ^a -> ^a) and
         ^a : (static member ( - ) : ^a * ^a -> ^a) =
  let rec step x i =
    if i >= 100 then x
    else step (x - alpha*(f' x)) (i+1)
  step x0 0

... but at alpha*(f' x) it's still telling me:

"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'."

.. which results in the same all-float type signature as above.

Upvotes: 2

Views: 410

Answers (2)

Gus
Gus

Reputation: 26204

As the compiler points out, the problem is in this fragment: alpha*(f' x)

This is because alpha is a float and in f# the multiplication having a float as first operand expects another float, so it infers float for the second operand.

If the second operand was not generic, ie: int you can use the explicit conversion like this: int alpha * (f' x) but there is no way to express a call to the generic op_Explicit of the generic type in F#, you can try with member constraints but the same problem Ganesh pointed out in the F# spec will apply here as well for op_Explicit.

I had the same problem in the past while trying to simulate Haskell's generic math, particularly the generic function fromInteger but I solved it using overloading, you can use the same technique to create a generic fromFloat function or use a generic convert function as the one I just added to F#+

Sample code:

#r @"FsControl.Core.dll"
#r @"FSharpPlus.dll"

open FSharpPlus

let inline fmingd (f':^a -> ^a) (x0:^a) (alpha:float) : ^a =
  let rec step x i =
    if i >= 100 then x
    else step (x - convert alpha * (f' x)) (i+1)
  step x0 0

Upvotes: 1

Ganesh Sittampalam
Ganesh Sittampalam

Reputation: 29120

It looks like ( * ) is treated specially somehow. Changing your code to this worked:

let inline fmingd (f':^a -> ^a) (x0:^a) (alpha:float) : ^a
    when ^a : (static member times : float * ^a -> ^a) and
         ^a : (static member ( - ) : ^a * ^a -> ^a) =
  let rec step x i =
    if i >= 100 then x
    else step (x - (^a : (static member times : float * ^a -> ^a) (alpha, f' x))) (i+1)
  step x0 0

And then when I tried this:

let inline fmingd (f':^a -> ^a) (x0:^a) (alpha:float) : ^a
    when ^a : (static member ( * ) : float * ^a -> ^a) and
         ^a : (static member ( - ) : ^a * ^a -> ^a) =
  let rec step x i =
    if i >= 100 then x
    else step (x - (^a : (static member ( * ): float * ^a -> ^a) (alpha, f' x))) (i+1)
  step x0 0

I got back to your original error but also got this one:

Member constraints with the name 'op_Multiply' are given special status by the F# compiler as certain .NET types are implicitly augmented with this member. This may result in runtime failures if you attempt to invoke the member constraint from your own code.

Unfortunately I'm not sure if calling the required member times will work too well for you given that you want to use other people's types.

EDIT:

I think it might be caused by this point in the F# language specification

14.5.4.1 Simulation of Solutions for Member Constraints

Certain types are assumed to implicitly define static members even though the actual CLI metadata for types does not define these operators. This mechanism is used to implement the extensible conversion and math functions of the F# library including sin, cos, int, float, (+), and (-). The following table shows the static members that are implicitly defined for various types.

And it goes on to mention float as one of the things that has op_Multiply (i.e. ( * )) specially defined.

Upvotes: 3

Related Questions