2
votes

Having a C# class like

public abstract class ValueAttrProxy<T> : IAttrProxy where T : IEquatable<T>
{
    public T Value { get; }
    ...
}

in F# when I try to pattern match it like so:

let attrValue (attr:IAttrProxy) =
    match attr with
    | :? ValueAttrProxy<'a> as attr -> attr.Value.ToString()

The type inference seems to work, but sending to interactive fails with the following error:

error FS0071: Type constraint mismatch when applying the default type 'IEquatable<'a>' for a type inference variable. The types ''a' and 'IEquatable<'a>' cannot be unified. Consider adding further type constraints

I am a bit stumped what is the problem, or where is the additional type annotation is expected.

Trying to specify IEquatable<'a> in the matching pattern like

| :? ValueAttrProxy<IEquatable<'a>> as attr -> attr.Value.ToString()

then even the type inference fails, and underlines the pattern with the same error message. If I constrain the generic parameter to a specific type like int, then it works, but the point is that I just want the string representation of the Value, regardless of what its actual type is.

1

1 Answers

3
votes

The issue is that when you write :? ValueAttrProxy<'a>, the compiler needs to infer the type of 'a statically. This is not a type that would be determined at runtime based on the value. If there are no constraints, the compiler will still happily compile this, but it will use obj as the default type of 'a.

This question is basically the same as a recent one on pattern matching against seq<'a> where you have exactly the same issue. The answer there shows how to solve the problem using reflection and this would work in your case too.

If you are in control of the C# classes, then it would be much easier to add BoxedValue property to the non-generic interface though:

public interface IAttrProxy {
    public object BoxedValue { get; }
    // (...)
}
public abstract class ValueAttrProxy<T> :
  IAttrProxy where T : IEquatable<T> {
    public T Value { get; }
    // (...)
}

You can then just pattern match against IAttrProxy and access the value directly:

let attrValue (attr:IAttrProxy) =
    attr.BoxedValue.ToString()