I am trying to implement a generic Vector< ^F>
class in F#, where ^F
is the underlying field type of the vector's elements.
That means, ^F
can be anything that satisfies adding, subtracting, multiplying, and negating.
Some example user code could look like that:
let v = Vector<int>([| 1; 2; 3 |])
let w = Vector<int>([| 4; 5; 6 |])
let sum = v + w
So here I use type int
, and it could be anything that satisfies the underlying basic operations noted above. I seem to get some version working when using .NET style generics, but there I'm having issues too. Since I want to use SRTP anyways, I went this route again:
type Vector< ^F when ^F : (static member (~-): ^F -> ^F)
and ^F : (static member (+): ^F * ^F -> ^F)
and ^F : (static member (*): ^F * ^F -> ^F)
>(_values: ^F[]) =
let values: ^F [] = _values
member inline this.Values = values
member inline this.Dimension = Array.length values
// Constructs a Vector using given initializer
static member inline Init (n: int) (initializer: (int -> ^F)) =
Vector< ^F>(Array.init n (fun i -> initializer (i + 1)))
member inline this.Item with get (i: int) = values.[i - 1]
// negate a vector
static member inline ( ~- ) (a: Vector< ^F>) =
Vector< ^F>.Init (Array.length a.Values) (fun i -> -a.[i])
// sum of two vectors
static member inline ( + ) (a: Vector< ^F>, b: Vector< ^F>): Vector< ^F> =
Vector< ^F>.Init (Array.length a.Values) (fun i -> a.[i] + b.[i])
// difference of two vectors
static member inline ( - ) (a: Vector< ^F>, b: Vector< ^F>): Vector< ^F> =
Vector< ^F>.Init (Array.length a.Values) (fun i -> a.[i] + (-b.[i]))
// scale vector by scalar
static member inline ( * ) (a: ^F, b: Vector< ^F>): Vector< ^F> =
Vector< ^F>.Init (Array.length b.Values) (fun i -> a * b.[i])
But the errors I am getting are motivation-exhausting, such as:
A type parameter is missing a constraint 'when ( ^F or ^?12844) : (static member ( + ) : ^F * ^?12844 -> ^F)'
(but also on operator -
and *
)The value 'Values' was marked inline but its implementation makes use of an internal or private function which is not
- this is basically all over the place, and is being reported for every member property, member function and static member function.(The code snipped above is copy'n'paste-compatible for easy reproduction)
How do I solve the problem of constructing a type, such as Vector, whos elements and operations all have a statically resolved type parameter (or generic) with no errors.
SRTP errors are horrible to diagnose, and it's considered a bug that the internally generated type ID leaks into user space. It's arguably not clear with the error message, but the static members don't satisfy all constraints. If you factor out the operators into a separate module, then type inference is able to take care of applying all constraints (as of F# 4.6):
type Vector< ^F when ^F : (static member (~-): ^F -> ^F)
and ^F : (static member (+): ^F * ^F -> ^F)
and ^F : (static member (*): ^F * ^F -> ^F)
>(_values: ^F[]) =
let values: ^F [] = _values
member inline __.Values = values
member inline __.Dimension = Array.length values
// Constructs a Vector using given initializer
static member inline Init (n: int) (initializer: (int -> ^F)) =
Vector< ^F>(Array.init n (fun i -> initializer (i + 1)))
member inline __.Item with get (i: int) = values.[i - 1]
[<AutoOpen>]
module VectorOps =
let inline ( ~- ) (v: Vector< ^F>) =
Vector< ^F>.Init (Array.length v.Values) (fun i -> -v.[i])
let inline ( + ) (a: Vector< ^F>) (b: Vector< ^F>) =
Vector< ^F>.Init (Array.length a.Values) (fun i -> a.[i] + b.[i])
let inline ( - ) (a: Vector< ^F>) (b: Vector< ^F>) =
Vector< ^F>.Init (Array.length a.Values) (fun i -> a.[i] - b.[i])
let inline ( * ) (k: ^F) (v: Vector< ^F>) =
Vector< ^F>.Init (Array.length v.Values) (fun i -> k * v.[i])
You can then use this as expected:
let v1 = Vector<int>([|1;2;3;4;5|])
let v2 = Vector<int>([|1;2;3;4;5|])
let negv1 = -v1
negv1.Values |> Array.iter (fun x -> printfn "%d " x)
let sum = v1 + v2
sum.Values |> Array.iter (fun x -> printfn "%d " x)
let diff = sum - v2
diff.Values |> Array.iter (fun x -> printfn "%d " x)
let scaled = 3 * diff
scaled.Values |> Array.iter (fun x -> printfn "%d " x)
Unfortunately, this is wading into the kind of territory where you run into nasty ends of the F# compiler. SRTP wasn't really designed for this style of programming, and though it certainly works, that lack of intention for this scenario really does leak out in the quirks and error messages.
I want to suggest you an existing solution for what you want to do.
The library FSharpPlus provides many solutions to deal with this in a consistent way.
I think a nice feature for this type of vectors is Applicative Math Operators (see the code section titled "Using applicative math operators").
Here's an example with your code:
#r @"FSharpPlus.dll"
open FSharpPlus
open FSharpPlus.Data
open FSharpPlus.Math.Applicative
let v = ZipList [| 1; 2; 3 |]
let w = ZipList [| 4; 5; 6 |]
let sum = v .+. w
// val it : ZipList<int> = ZipList (seq [5; 7; 9])
let mul = v .*. w
// val it : ZipList<int> = ZipList (seq [4; 10; 18])
You can also do operations against scalars:
v .* 6 ;;
// val it : ZipList<int> = ZipList (seq [6; 12; 18])
7 +. w ;;
val it : ZipList<int> = ZipList (seq [11; 12; 13])
The underlying abstraction used here are Applicative Functors, since a vector (think of a ZipList as a Vector) is an Applicative Functor it simply works.
ZipList, are a bit standard in the FP world, but alternatively you can also have a look at the ParallelArray applicative. If you don't like the names you can copy-paste the code and change the definition to:
type TVector<'t> =
| Scalar of 't
| Vector of 't array
...
let v = Vector [| 1; 2; 3 |]
v .* 6 ;;
// or
v .*. Scalar 6
And finally, if you don't want to use the library, you can use it as inspiration and copy the code you need. It's just there, and it works.
NOTE
Regarding your original code, if you have a look at the sources I pointed you, you'll see that it uses a more functional approach and for instance it doesn't restrict the type parameter at the type level, instead it restrict the functions.
type Vector< ^F>(_values: ^F[]) =
let values: ^F [] = _values
member inline this.Values = values
member inline this.Dimension = Array.length values
// Constructs a Vector using given initializer
static member inline Init (n: int) (initializer: (int -> 'f)) =
Vector<'f>(Array.init n (fun i -> initializer (i + 1)))
member inline this.Item with get (i: int) = values.[i - 1]
// negate a vector
static member inline ( ~- ) (a: Vector<'f>) =
Vector<'f>.Init (Array.length a.Values) (fun i -> -a.[i])
Note how capital ˆF
is only used at the type level, whereas in the functions (or static members) a different type parameter is used, lowercase 'f
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With