Reputation: 171
I have a generic class with the default constructor accepting an argument of the generic type. When creating a second, non-generic constructor, the F# compiler complains that This type parameter has been used in a way that constrains it to always be 'EmptyType'
, and I don't understand this error at all. Why does it constrains? What does the second constructor has to do with the generic property of the first?
This example shows the error (reproducible in F# playground). Notice that simply commenting the second constructor (line 9), solves the compilation issue:
type MyInterface = interface end
type EmptyType() = interface MyInterface
type RealType(v: int) =
member this.Value = v
interface MyInterface
type MyType<'T when 'T :> MyInterface>(element: 'T) =
new() = MyType(EmptyType())
member this.X = 0
[<EntryPoint>]
let main _ =
//let a = MyType() //empty constructor, 'T is EmptyType
let b = MyType(RealType(0)) //doesnt work because compiler says 'T is always EmptyType? what?
0
Upvotes: 3
Views: 59
Reputation: 80714
Constructors cannot be generic. Your type is generic, but each constructor must return the exact type on which it is defined. You cannot have one constructor return MyType<'t>
and another return MyType<EmptyType>
. If a constructor is defined on MyType<'t>
, it must return MyType<'t>
and cannot return MyType<EmptyType>
When the compiler sees that one of the constructors always returns MyType<EmptyType>
, it concludes that type parameter 't
must always be equal to EmptyType
. It is in this sense that the parameter 't
becomes constrained to EmptyType
, as the error message says.
There is no way to have a constructor that returns a different type than the one on which it's defined. If you have to construct instances of MyType<EmptyType>
specifically, you can use a static method instead:
type MyType<'t>(t: 't) =
static member CreateEmpty () = MyType(EmptyType())
However, note that to call such method you would still have to provide some type parameter for MyType
, e.g.:
let x = MyType<RealType>.CreateEmpty()
Or you can use a wildcard for it:
let x = MyType<_>.CreateEmpty()
With no basis to infer what the wildcard should be, the compiler will fall back to obj
(or to the nearest constraint), and the above call will thus be equivalent to MyType<obj>.CreateEmpty()
. Still awkward.
To avoid this, a better way is to use a module with same name:
module MyType =
let createEmpty() = MyType(EmptyType)
let x = MyType.createEmpty()
This pattern is widely used with generic types. See, for example, Option
or List
.
Upvotes: 4
Reputation: 1685
Unsure what the specific limitation on overloaded constructors is that is causing T
to be constrained in this scenario however the following code using static member
instead works as desired:
type MyInterface = interface end
type EmptyType() = interface MyInterface
type RealType(v: int) =
member this.Value = v
interface MyInterface
type MyType<'T when 'T :> MyInterface>(element: 'T) =
static member Empty() = MyType(EmptyType())
member this.X = 0
[<EntryPoint>]
let main _ =
let a = MyType<_>.Empty()
let b = MyType(RealType(0))
0
Upvotes: 2