Akash
Akash

Reputation: 2371

F# cryptic generic constraint error message

Just for fun I've been playing around with emulating typeclasses in F#, using the ideas shown here. I created a Next typeclass to represent values that have a "successor" e.g. next 1 = 2, next today = tomorrow etc:

type Next = Next with
    static member (++) (Next, x:int) = x + 1
    static member (++) (Next, x:DateTime) = x.AddDays 1.0
let inline next x = Next ++ x 

let v1 = next 1 // v1 = 2
let v2 = next DateTime.Now // v2 = Now + 1 day

Now I want to use "nextable" types in a generic class:

type UsesNextable<'T>(nextable: 'T) = // Compile error 1
    member inline this.Next = 
        let v = next nextable         // Compile error 2
        v.ToString()

But I get the following errors:

  1. The signature and implementation are not compatible because the type parameter in the class/signature has a different compile-time requirement to the one in the member/implementation
  2. The signature and implementation are not compatible because the declaration of the type parameter 'T' requires a constraint of the form (Next or ^T) : (static member ( ++ ) : Next * ^T -> ^?155882)

I'm more interested in what the second error is trying to say. What does the cryptic ^?155882 mean? I'm guessing that it might have something to do with being unable to resolve the type. Also, assuming that it is trying to infer the constraint to be:

(Next or  ^T) : (static member ( ++ ) : Next *  ^T ->  ^T)

Is it even possible to specify the "or" condition using when 'T:... ? I can't figure out a valid syntax.

Finally, ^T suggests a statically resolved type parameter, but the documentation says that they cannot be used on types. Despite this, I tried changing the 'T to ^T but still got the same errors.

I realise that this example is abusing the type system, so I'm not suggesting that the compiler should be able to handle it. However, for my own interest I'd like to know what the error messages really mean!

Upvotes: 0

Views: 290

Answers (1)

Gus
Gus

Reputation: 26204

The problem is that .NET doesn't support static constraints. F# resolves them inlining functions which are compiled to static methods, but can't encode it into a standard .NET type. In your code the generic type you want to create would have a type parameter with a static constraint which .NET is not able to represent.

You can change your method to static and it will work fine:

type UsesNextable() =
    static member inline Next(nextable) = 
        let v = next nextable
        v.ToString()

Your type still can be generic if you want, but you should avoid referencing the type parameter of the type with a static constraint, for instance this will work:

type UsesNextable<'T>() =
  static member inline NextOf(n:'U) = let v = next n in v.ToString()

But not this:

type UsesNextable<'T>() =
  static member inline NextOf(n:'T) = let v = next n in v.ToString()

Having solved the first problem, you will see both error messages disappear, because the second was related to the first one where the type system was unable to encode the constraint, but now it can figure it out alone. ^?155882 represents a static-constrained type variable which the type system was unable to infer.

Note that changing from 'T to ˆT doesn't change anything, actually both refers to the same type variable, the only thing is that you're forced to use the hat when you call the method.

Finally regarding the fact that you can not write:

let inline next v = ((Next or  ^T) : (static member ( ++ ) : Next *  ^T ->  ^T) Next, v)

that's clearly a bug, because F# can infer it but you can't write it which is not consistent. I already reported it some time ago and they told me that they will fix it in the future.

There are many ways to workaround that limitation by forcing F# to infer the type instead of writing it directly, here's a one liner solution:

let inline next v = ((^Next or  ^T) : (static member ( ++ ) : ^Next *  ^T ->  ^T) Next, v)

It will create a warning but you can disable it by nowarn directive or by adding an parameter to the function containing the instance of the type Next.

Upvotes: 5

Related Questions