fnzr
fnzr

Reputation: 171

Compiler error in F# when creating a secondary, non-generic constructor

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

Answers (2)

Fyodor Soikin
Fyodor Soikin

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

tranquillity
tranquillity

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

Related Questions