Alexander
Alexander

Reputation: 25

Trouble with generics

Why doesn't the following code compile? Is that a compiler bug or a language feature? What is the best workaround?

type A() as this =
    let a = this.GetTypedObject<int>()    // a
    let b = this.GetTypedObject<string>() // b

    member this.GetTypedObject<'T>() =
        Unchecked.defaultof<'T>

Unchecked.defaultof<'T> is used for example only, any function or constructor call could be used instead.

Compiler says that code becomes less generic at line (a) and refuses to compile line (b). I cannot give exact compiler messages because I have them in Russian :).

Turning method GetTypedObject() into a let binding and removing <'T> ends up with another compiler warning which I don't like either. The only wokaround I found is to move GetTypedObject<'T>() into a base class and to make it public. Thanks in advance...

Upvotes: 0

Views: 189

Answers (2)

Daniel
Daniel

Reputation: 47904

Since type inference works top-to-bottom, it encounters GetTypedObject returning int before it even reaches the method definition and discovers it should be generic. (As Brian points out, members are read prior to let bindings...and there's a simpler fix for this.) I get the following nasty error:

A use of the function 'GetTypedObject' does not match a type inferred elsewhere. The inferred type of the function is Test.A -> Microsoft.FSharp.Core.unit -> 'a. The type of the function required at this point of use is Test.A -> Microsoft.FSharp.Core.unit -> 'a This error may be due to limitations associated with generic recursion within a 'let rec' collection or within a group of classes. Consider giving a full type signature for the targets of recursive calls including type annotations for both argument and return types.

If the usage appears after the method definition, it works.

type A() =
    member this.GetTypedObject<'T>() =
        Unchecked.defaultof<'T>

    member this.Test() =
      let a = this.GetTypedObject<int>()    // a
      let b = this.GetTypedObject<string>() // b
      ()

Here's a workaround:

type A() =
  let getTypedObject() = Unchecked.defaultof<_>
  let a : int = getTypedObject()    // a
  let b : string = getTypedObject() // b

  member this.GetTypedObject<'T>() : 'T = getTypedObject()

Upvotes: 3

Brian
Brian

Reputation: 118865

Note that you can simply add a type annotation to fix this:

type A() as this = 
    let a = this.GetTypedObject<int>()    // a 
    let b = this.GetTypedObject<string>() // b 

    member this.GetTypedObject<'T>() : 'T = 
                                   //^^^^
        Unchecked.defaultof<'T> 

Member signatures get read first, then all the let and member bodies, when it comes to order of type inference. By putting the return type in the declaration signature, it becomes visible to the lets.

Upvotes: 4

Related Questions