Mike S
Mike S

Reputation: 166

FSharp: Type inference with generic

I do not understand why the following code does not compile

module GenericsTest = 
    open System

    type Dog = {
        name:string
    }

    type Apple = {
        size:int
    }

    let get<'a> (id:string) =
        Activator.CreateInstance<'a>()

    let creatorInferred getAsParam = 
        let apple = {
            name = getAsParam "some-apple"
        }

        let dog = {
            size = getAsParam "some-dog"
        }
        (apple, dog)

    let creatorWithTypeAnnotation (getAsParam:string->'a) = 
        let apple = {
            name = getAsParam "some-apple"
        }

        let dog = {
            size = getAsParam "some-dog"
        }
        (apple, dog)

If you look at the 2 "creator..." function - both of them give the compile error..

this expression was expected to have the type int... but here has the type string

I can see that F# is infering the return type of the getAsParam method to be int, because it is the first one that it encounters. However, why does it not then decide to use a generic return type?

As you can see, i have tried to for the function signature in the creatorWithTypeAnnotation method - but this has no affect.

I'm stumped! How do i force this to recognise that the getAsParam function should return a generic?

Upvotes: 0

Views: 320

Answers (2)

phoog
phoog

Reputation: 43036

As Carsten implies, the type variables are all resolved at compile time. If you define getAsParam as string -> 'a, that doesn't stop the 'a from being resolved at compile time. Because it cannot resolve at compile time to two different types, compilation fails.

Consider your example:

let creatorWithTypeAnnotation (getAsParam:string->'a) = 
    let apple = {
        name = getAsParam "some-apple"
    }

    let dog = {
        size = getAsParam "some-dog"
    }

This could also be declared thus (I'll use 'A instead of 'a, since the intended convention is that lower-case type variables are for compiler-inferred types):

let creatorWithTypeAnnotation<'A> (getAsParam:string->'A) = 
    let apple = {
        name = getAsParam "some-apple"
    }

    let dog = {
        size = getAsParam "some-dog"
    }

Now it should be clear why the code does not compile.

Upvotes: 1

Random Dev
Random Dev

Reputation: 52270

here is a quick F#-interactive session on what I meant with cheat by using an interface (it just have to be some sort of member - so it can be a method on a class too of course):

> type IParam = abstract getParam : string -> 'a;;                              

type IParam =
  interface
    abstract member getParam : string -> 'a
  end

> let createWith (p : IParam) : int*bool = (p.getParam "a", p.getParam "b");;            

val createWith : p:IParam -> int * bool

> let test = { new IParam with member __.getParam s = Unchecked.defaultof<_> };;

val test : IParam

> createWith test;;                                                             
val it : int * bool = (0, false)

you might find it not exactly easy to implement some saner instances of IParam though ;)

Upvotes: 2

Related Questions