Matthew Crews
Matthew Crews

Reputation: 4295

F# type error with unresolved type at compile time

I'm trying an experiment where I am wrapping some arrays to ensure the units on the index. I am getting a compiler error on the last line of code here. It is saying that the type parameter 'Value' cannot be used since the type parameter cannot be resolved at compile time. I cannot figure out why this is causing an error. I also tried changing the method signature to restrict the 'Value to be the same in both arrays.

type ImArr<[<Measure>] 'Index, 'Value>(values: array<'Value>) =
    // We want to make sure we have our own copy to protect against mutation
    let values = values |> Array.copy

    member internal _.Values = values

    member this.Item
        with get(index: int<'Index>) =
            values.[int index]


type Arr<[<Measure>] 'Index, 'Value>(values: array<'Value>) =
    // We want to make sure we have our own copy to protect against mutation
    let values = values |> Array.copy

    member internal _.Values = values

    member inline _.Add (x: ImArr<'Index,_>) =
        if values.Length <> x.Values.Length then
            invalidArg (nameof x) "Cannot add arrays of different lengths"

        for idx = 0 to values.Length - 1 do
            values.[idx] <- values.[idx] + x.Values.[idx] // Error on this line

The other method signature I tried which also raises an error.

member inline _.Add (x: ImArr<'Index,_>) =

Image of error message: enter image description here

I also tried pulling the work out into a separate function and got the same result.

type ImArr<[<Measure>] 'Index, 'Value>(values: array<'Value>) =
    // We want to make sure we have our own copy to protect against mutation
    let values = values |> Array.copy

    member internal _.Values= values

    member this.Item
        with get(index: int<'Index>) =
            values.[int index]


type Arr<[<Measure>] 'Index, 'Value>(values: array<'Value>) =
    // We want to make sure we have our own copy to protect against mutation
    let values = values |> Array.copy

    member this.Item
        with get(index: int<'Index>) =
            values.[int index]

        and set(index: int<'Index>) (value: 'Value) =
            values.[int index] <- value

    member internal _.Values = values


module Helpers =

    let inPlaceAdd<[<Measure>] 'Index, 'Value> (a: Arr<'Index, 'Value>) (b: ImArr<'Index, 'Value>) =
        let mutable i = 0;
        while i < a.Values.Length && i < b.Values.Length do
            a.Values.[i] <- a.Values.[i] + b.Values.[i] // ERROR
            i <- i + 1

And the compiler error

              a.Values.[i] <- a.Values.[i] + b.Values.[i]
  ----------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^

stdin(31,29): error FS0001: The declared type parameter 'Value' cannot be used here since the type parameter cannot be resolved at compile time

Upvotes: 2

Views: 87

Answers (2)

Gus
Gus

Reputation: 26174

This is a known issue with the F# compiler: it's better to let F# compiler infer constraints than write them by hand.

Here's a solution that allows F# compiler to infer it:

module Helpers =

    let inline inPlaceAdd<[<Measure>] 'Index, .. > (a: Arr<'Index, 'Value>) (b: ImArr<'Index, 'Value>) : unit =
        let mutable i = 0;
        while i < a.Values.Length && i < b.Values.Length do
            a.Values.[i] <- a.Values.[i] + b.Values.[i] // ERROR
            i <- i + 1

Easier to write, and it doesn't produce any warning.

Upvotes: 1

Brian Berns
Brian Berns

Reputation: 17028

I think the main problem here is that the compiler doesn't know how to add two generic 'Values together. Since the compiler can't infer how to do this, you probably need to give it a hint via statically resolved type parameters (SRTP), which are, unfortunately, very flaky in F#. By trial and error, I came up with a version of your helper function that compiles with a warning but seems to work:

let inline internal inPlaceAdd<[<Measure>] 'Index, ^Value when ^Value : (static member (+) : ^Value * ^Value -> ^Value)> (a: Arr<'Index, 'Value>) (b: ImArr<'Index, 'Value>) =
    let mutable i = 0;
    while i < a.Values.Length && i < b.Values.Length do
        let x = (^Value : (static member (+) : ^Value * ^Value -> ^Value)(a.Values.[i], b.Values.[i]))
        a.Values.[i] <- x
        i <- i + 1

Note the extremely convoluted invocation of addition, which causes compiler warning FS0077: Member constraints with the name 'op_Addition' are given special status by the F# compiler.... By all rights, you should be able to add values directly instead, like this:

let x = a.Values.[i] + b.Values.[i]

But that doesn't compile, for some reason: A type parameter is missing a constraint 'when ( ^Value or ^?11121) : (static member ( + ) : ^Value * ^?11121 -> ^?11122)' This looks like a compiler bug to me, but who knows.

Usage:

let a = Arr([|1; 2; 3|])
let b = ImArr([|2; 3; 4|])
Helpers.inPlaceAdd a b
printfn "%A" a.Values   // [|3; 5; 7|]

Upvotes: 2

Related Questions