Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't F# infer the type in this case?

Tags:

f#

Consider the following sample code where I have a generic type and 2 static member constructors that create a specialized instance of the said type.

type Cell<'T> = { slot: 'T }
with
    static member CreateInt x : IntCell  = { slot = x }
    static member CreateString x : StringCell = { slot = x}
and IntCell = Cell<int>
and StringCell = Cell<string>

// Warnings on the next 2 lines
let x = Cell.CreateInt 123
let y = Cell.CreateString "testing"

I think I have the necessary type annotations in place and yet F# gives me warnings. E.g:

Warning 2 The instantiation of the generic type 'Cell' is missing and can't be inferred from the arguments or return type of this member. Consider providing a type instantiation when accessing this type, e.g. 'Cell<_>'.

How can I make the warning go away?

like image 904
rgrinberg Avatar asked Feb 12 '23 12:02

rgrinberg


2 Answers

As hinted by @ildjarn, Cell is a generic type and the compiler wants to know the type 'T when calling the static member.

// Two ways to fix the compiler warning
let x = Cell<int>.CreateInt 123
let y = StringCell.CreateString "testing"

A way to avoid specifying 'T is to move the create functions into a module.

type Cell<'T> = { slot: 'T }
type IntCell = Cell<int>
type StringCell = Cell<string>
module Cell =
    let createInt x : IntCell = { slot = x }
    let createString x : StringCell = { slot = x }

let x = Cell.createInt 123
let y = Cell.createString "testing"

However, since you specify the desired type in the function name anyway, the following syntax may be preferred.

type Cell<'T> = { slot: 'T }
with
    static member Create (x : 'T) = { slot = x }
type IntCell = Cell<int>
type StringCell = Cell<string>

let x = IntCell.Create 123
let y = StringCell.Create "testing"

// or simply
let z = Cell<float>.Create 1.0

Thanks to @Vandroiy for pointing out the missing type constraint in my Create method and for his answer that shows how the compiler can infer 'T for the generic type Cell when it can be determined by the static method being called.

like image 65
cadull Avatar answered Feb 16 '23 01:02

cadull


The compiler cannot determine the generic parameter 'T of the methods CreateInt and CreateFloat because it is unrelated to the methods' return types. In the question, it is valid to write:

Cell<float>.Create 1.0 // useless type annotation to remove warning

However, you can just as well write

Cell<string>.Create 1.0 // Trollolol

To avoid this, you need to make sure the factory can only produce the type it is called on. When declaring a factory on a generic type, use a type annotation to equate the generic argument of its return type with the generic argument of the type it is called on.

In my opinion, the complicated formulation adds to the confusion. You can achieve the desired effect with

type Cell<'T> =
    { slot: 'T }
    static member Create (x : 'T) = { slot = x }

let x = Cell.Create 123
let y = Cell.Create "testing"

Note the type annotation for x that equates the factory's input type with the generic parameter of the Cell<> type!

Edited to address the comment:

As is, the types IntCell and StringCell serve no purpose; they are just a less readable form of Cell<int> and Cell<string>. From a comment to this answer, I understand that these types should be exposed instead of Cell. As far as I know, this is not possible if they are defined as in the question, since type abbreviations have at most the accessibility of the type they abbreviate.

This is a reasonable design choice: if a type is generic, it should accept all valid generic type arguments. If IntCell and StringCell add specialized implementation, the usual way is to compose them of the appropriate instantiation of the Cell type and their specialized features. Then, the Cell type is allowed to have a more restricted accessibility than the specialized types.

like image 26
Vandroiy Avatar answered Feb 16 '23 01:02

Vandroiy