简体   繁体   中英

Adding member constraint to an inlined function's paramater causes FS0752 on array accessor

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM