Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(FS0193, FS1113) F# Vector class with Statically Resolved Type Parameters

Tags:

types

generics

f#

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:

  • FS0193 (warning): A type parameter is missing a constraint 'when ( ^F or ^?12844) : (static member ( + ) : ^F * ^?12844 -> ^F)' (but also on operator - and *)
  • FS1113 (error): 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.

like image 765
christianparpart Avatar asked May 15 '19 22:05

christianparpart


2 Answers

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.

like image 64
Phillip Carter Avatar answered Oct 12 '22 17:10

Phillip Carter


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.

like image 31
Gus Avatar answered Oct 12 '22 17:10

Gus