I'm trying to constrain an F# function parameter to conform to multiple interfaces, which are in turn derived from the same generic interface declaration:
module GenericInterfaceTest =
(* declare an interface using generic type *)
type IReader<'a> =
abstract member readValue : 'a
(* constraining only int works *)
let useReaderInt<'r when 'r :> IReader<int>> (reader : 'r) =
(reader :> IReader<int>).readValue
(* constraining only float works *)
let useReaderFloat<'r when 'r :> IReader<float>> (reader : 'r) =
(reader :> IReader<float>).readValue
(* [FS0193] Type constraint mismatch.
The type ''r' is not compatible with type 'IReader<float>' *)
let useReaders<'r when 'r :> IReader<int> and 'r :> IReader<float>> (reader : 'r) =
(reader :> IReader<int>).readValue, (reader :> IReader<float>).readValue
(* [FS0193] Type constraint mismatch.
The type ''r' is not compatible with type 'IReader<int>' *)
let useReaders<'r when 'r :> IReader<float> and 'r :> IReader<int>> (reader : 'r) =
(reader :> IReader<int>).readValue, (reader :> IReader<float>).readValue
(* ------------------------------------------------------------------------
Not using generic type works
------------------------------------------------------------------------ *)
type IIntReader =
abstract member readValue : int
type IFloatReader =
abstract member readValue : float
(* works *)
let useReaders<'r when 'r :> IIntReader and 'r :> IFloatReader> (reader : 'r) =
(reader :> IIntReader).readValue, (reader :> IFloatReader).readValue
(* ------------------------------------------------------------------------
Using different generic declarations works
------------------------------------------------------------------------ *)
type IReader2<'a> =
abstract member readValue : 'a
(* works *)
let useReaders<'r when 'r :> IReader<int> and 'r :> IReader2<float>> (reader : 'r) =
(reader :> IReader<int>).readValue, (reader :> IReader2<float>).readValue
(* ------------------------------------------------------------------------
Using type aliases does not work
------------------------------------------------------------------------ *)
type IReaderInt = IReader<int>
type IReaderFloat = IReader<float>
(* [FS0193] Type constraint mismatch.
The type ''r' is not compatible with type 'IReaderInt' *)
let useReaders<'r when 'r :> IReaderFloat and 'r :> IReaderInt> (reader : 'r) =
(reader :> IReader<int>).readValue, (reader :> IReader2<float>).readValue
Is this a compiler bug or limitation, or am I doing something wrong?
Is there another way of achieving this without having to write the code multiple times using concrete types?
Edit: I have found the issue on fslang-suggestions: https://github.com/fsharp/fslang-suggestions/issues/1036
It is a limitation involving type inference.
One of the comments mentions working around this by having intermediate non-generic interfaces inheriting generic specialisations. I would be very grateful if someone could help me with an example of how to do this - my best guess below does not work:
module GenericInterfaceTest2 =
(* declare an interface using generic type *)
type IReader<'a> =
abstract member readValue : 'a
type IReaderInt = inherit IReader<int>
type IReaderFloat = inherit IReader<float>
(* works *)
let useReaders<'r when 'r :> IReaderInt> (reader : 'r) =
(reader :> IReader<int>).readValue
(* [FS0193] Type constraint mismatch.
The type ''r' is not compatible with type 'IReaderFloat' *)
let useReaders<'r when 'r :> IReaderInt and 'r :> IReaderFloat> (reader : 'r) =
(reader :> IReaderInt).readValue, (reader :> IReaderFloat).readValue