0
votes

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?

2
basically when it sees the first (the Apple) the type-inference will specialize getAsParam into string -> Apple, so of course your second usage of it (for the Dog) must fail - you could even see it if you remove the part with the error - the compiler should warn you that it constrained 'a into Apple - just try to think how you would write a function returning a real generic 'a without cheating (reflection, defaults, ...) - Random Dev
but.. how do i force getAsParam to have a generic return type? Note - i can make this happen if one of my arguments to getAsParams is 'a ... but i dont want to do that - Mike S
you can cheat your way into this if you use an interface instead of a function for getAsParam - Random Dev
just think of the 'a in your getAsParam as an generic parameter to createorWithAnnotation - see why it will not can be two different things here? - so if you need two different results you have to use two different getAsParam1 and getAsParam2 arguments too - Random Dev
See stackoverflow.com/questions/11547804/… for related commentary. - kvb

2 Answers

2
votes

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 ;)

1
votes

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.