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
CodePudding user response:
This isn't pretty, but it compiles:
type IReaderIntFloat =
inherit IReader<int>
inherit IReader<float>
let useReaders (reader : IReaderIntFloat) =
(reader :> IReader<int>).readValue, (reader :> IReader<float>).readValue
CodePudding user response:
You can use non-generic wrapper types and flexible types:
// Input type
type IReader<'a> =
abstract member readValue : 'a
// Wrapper types
type IReaderInt =
abstract readInt : int
type IReaderFloat =
abstract readFloat : float
// Implementation
type NumberReader(intReader: IReader<int>, floatReader: IReader<float>) =
interface IReaderInt with
member _.readInt = intReader.readValue
interface IReaderFloat with
member _.readFloat = floatReader.readValue
// Helpers
let readInt (reader: #IReaderInt) =
reader.readInt
let readFloat (reader: #IReaderFloat) =
reader.readFloat
// Final function
let readNumbers reader =
readInt reader, readFloat reader
// val readNumbers:
// reader: 'a -> int * float when 'a :> IReaderInt and 'a :> IReaderFloat
At first, I've tried using inheritance but type inference does not work any more