I've read again and again the official Microsoft docs on type constraints, but I can't understand why this code doesn't compile :
let inline transform<'A, 'a when 'A : (member Item : int -> float)> (a: 'A) : 'a =
a.[0]
error FS0752: The operator 'expr.[idx]' has been used on an object of indeterminate type based on information prior to this program point. Consider adding further type constraints
And with :
let inline transform<'A, 'a when 'A : (member f : int -> float)> (a: 'A) : 'a =
a.f(0)
error FS0072: Lookup on object of indeterminate type based on information prior to this program point. A type annotation may be needed prior to this program point to constrain the type of the object. This may allow the lookup to be resolved.
Obviously I didn't understand how to use member constraint with generics in f#.
The general problem I'm facing is that I want to make generic functions over "vector-like" types like standard float[]
, or Vector<float>
from MathNet.Numerics
or even DV
from the DiffSharp package.
Right now I have to get an ad hoc function for each type, like (full code) :
#I ".paket/load"
#load "mathnet.numerics.fsharp.fsx"
#load "diffsharp.fsx"
open DiffSharp.AD.Float64
open MathNet.Numerics
open MathNet.Numerics.LinearAlgebra
open MathNet.Numerics.LinearAlgebra.Double
let l1 = 4.5
let l2 = 2.5
let a0 = [1.1; -0.9]
let inline transformVec (a:Vector<float>) =
let x1, y1 = l1 * cos a.[0], l1 * sin a.[0]
let x2, y2 = x1 + l2 * cos (a.[0] + a.[1]), y1 + l2 * sin (a.[0] + a.[1])
vector [x1; y1; x2; y2]
let inline transformDV (a:DV) =
let x1, y1 = l1 * cos a.[0], l1 * sin a.[0]
let x2, y2 = x1 + l2 * cos (a.[0] + a.[1]), y1 + l2 * sin (a.[0] + a.[1])
toDV [x1; y1; x2; y2]
As you can see this functions do exactly the same things but operates on different types.
I would like to get a generic function like (not working code) :
let inline transform<'A, 'a when 'A : (member Item : int -> 'a)> (toExt : 'a list -> 'A) (a: 'A) : 'A =
let x1, y1 = l1 * cos a.[0], l1 * sin a.[0]
let x2, y2 = x1 + l2 * cos (a.[0] + a.[1]), y1 + l2 * sin (a.[0] + a.[1])
toExt [x1; y1; x2; y2]
let transformVec = transform vector
let transformDV = transform toDV
What am I missing?
Edit: I've got it half-working with Mathnet.Numerics
let inline transform (toExt : 'a list -> 'A) (a: 'A) : 'A =
let inline get i : 'a = (^A : (member get_Item: int -> 'a) a,i)
let x1, y1 = l1 * cos (get(0)), l1 * sin (get(0))
let x2, y2 = x1 + l2 * cos (get(0) + get(1)), y1 + l2 * sin (get(0) + get(1))
toExt [x1; y1; x2; y2]
(transform vector) (vector a0)
Because it enforces 'a
(warning FS0064
) to be float
, which I don't want... (DV
from DiffSharp
returns D
type on get_Item
, not float
.)
replacing the declaration by
let inline transform<'a> (toExt : 'a list -> 'A) (a: 'A) : 'A =
makes the compiler croaks:
error FS0001: The declared type parameter 'a' cannot be used here since the type parameter cannot be resolved at compile time
You need to call the member Item
like this:
let inline transform (a: 'A) : 'a = (^A : (member get_Item: _ -> _) a, 0)
However you'll get a warning,
~vs72B.fsx(2,5): warning FS0077: Member constraints with the name 'get_Item' 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.
because some primitive types use "simulated members". So for a list it will work:
transform ["element"]
// val it : string = "element"
but not for arrays
transform [|"element"|]
System.NotSupportedException: Specified method is not supported.
at <StartupCode$FSI_0009>.$FSI_0009.main@()
Stopped due to error
This is because the F# compiler pretends that arrays have that member, but in fact they don't.
If this is a problem, you can use a more complex solution with overloads to add special implementations for specific types, which is not straight-forward but I can show you how, or you may consider using F#+ which has an Indexable abstraction for types which typically have an Item
property.
Of course you can ignore that warning, with a #nowarn "77"
but as you've seen the compiler can not check that someone will call your function with an array and fail at runtime.
UPDATE
Since you asked as follow up question how to use it, here's an example:
#r "MathNet.Numerics.dll"
#r "MathNet.Numerics.FSharp.dll"
#r "FSharpPlus.dll"
open FSharpPlus
open MathNet.Numerics.LinearAlgebra
let x = item 1 [0..10]
let y = item 1 [|0..10|]
let z = item 1 (vector [0.;1.;2.])
// val x : int = 1
// val y : int = 1
// val z : float = 1.0
I'm not sure if it will work with DiffSharp and I don't know which type are you using from that library, I found DV
multiple times.
UPDATE2
Regarding your follow up question for your generic transform
function, using a simple member constraint will not be enough, you will also need to solve generically the conversion from a list to the destination type and also create a different generic multiplication which works generically but with the types you
need to deal with. You can use overloads combined with member constraint to get the desired functionality:
let inline item (i:int) (a: 'A) : 'a = (^A : (member get_Item: _ -> _) a, i)
type T = T with
static member ($) (T, _:Vector<float>) = fun (x:float list) -> vector x
static member ($) (T, _:Matrix<float>) = fun (x:float list) -> matrix [x]
static member ($) (T, _:DV ) = fun (x: D list ) -> toDV (List.toArray x)
let inline toDestType (x:'t list) :'D = (T $ Unchecked.defaultof<'D>) x
type V = V with
static member ($) (V, x:float ) = fun (y: float) -> x * y : float
static member ($) (V, x:D ) = fun (y: float) -> x * y : D
let inline mult (y:float) (x:'t) :'t = (V $ x) y
let inline transform (a:'T) :'T =
let x1, y1 = mult l1 (cos (item 0 a)), mult l1 (sin (item 0 a))
let x2, y2 = x1 + mult l2 (cos ((item 0 a) + (item 1 a))), y1 + mult l2 (sin ((item 0 a) + (item 1 a)))
let g = toDestType [x1; y1; x2; y2]
g
let b = transform (DV [| 1. ; 2.|])
let a = transform (vector [1. ; 2.])
I still get a runtime error each time I reference DiffSharp, however intellisense shows the right types inferred.