I ran into a situation that the type checker doesn't like:
type I<'a> = interface end
type IServices =
abstract member Register<'a, 'b when 'a :> I<'b>> : ctor: (IServices -> 'a) -> unit
type Foo(services: IServices) =
interface I<int>
interface I<string>
let register (services: IServices) =
services.Register<Foo, int> Foo
services.Register<Foo, string> Foo
Expected behavior
Compiles successfully.
Actual behavior
You get a compile error:
Restore complete (0.2s)
MailboxGenericsProblem net10.0 failed with 1 error(s) (1.0s)
C:\Code\FSharpSandbox\GenericsProblem\Program.fs(11,5): error FS0001: This expression was expected to have type 'string' but here has type 'int'
Build failed with 1 error(s) in 1.5s
Known workarounds
Interestingly, reordering the generic parameters on the method declaration satisfies the type checker:
type I<'a> = interface end
type IServices =
abstract member Register<'a, 'b when 'b :> I<'a>> : ctor: (IServices -> 'b) -> unit
type Foo(services: IServices) =
interface I<int>
interface I<string>
let register (services: IServices) =
services.Register<int, Foo> Foo
services.Register<string, Foo> Foo
Presumably this is due to the ordering and the eagerness in how the type checker unifies.
However, I suspect this workaround may not always possible, especially if you were to run into this in the wild with a C# library you don't control (though this hasn't happened to me yet).
More details
My real-world use case is of course a bit more complex: a flexible strongly-typed actor-like system where actors can accept multiple types of messages by implementing a generic interface, you can have a "weak" / "opaque" reference to an actor, etc.
I could also see this plausibly showing up in a service locator pattern.
Related information
- Windows 11
- .NET 5, 6, 7, 8, 9, 10
I found some similar issues:
but most of these are related to issues on the declaration side with multiple constraints, and sometimes type inference, rather than the caller side with specific concrete types with full annotations. I can see how the type checker would struggle with the other usages, but hopefully this particular situation is more plausible to deal with.
I ran into a situation that the type checker doesn't like:
Expected behavior
Compiles successfully.
Actual behavior
You get a compile error:
Known workarounds
Interestingly, reordering the generic parameters on the method declaration satisfies the type checker:
Presumably this is due to the ordering and the eagerness in how the type checker unifies.
However, I suspect this workaround may not always possible, especially if you were to run into this in the wild with a C# library you don't control (though this hasn't happened to me yet).
More details
My real-world use case is of course a bit more complex: a flexible strongly-typed actor-like system where actors can accept multiple types of messages by implementing a generic interface, you can have a "weak" / "opaque" reference to an actor, etc.
I could also see this plausibly showing up in a service locator pattern.
Related information
I found some similar issues:
but most of these are related to issues on the declaration side with multiple constraints, and sometimes type inference, rather than the caller side with specific concrete types with full annotations. I can see how the type checker would struggle with the other usages, but hopefully this particular situation is more plausible to deal with.