Search code examples
genericstypesf#inferenceseq

Type inference in sequence expressions in F#


I think I do not quite understand how F# infers types in sequence expressions and why types are not correctly recognized even if I specify the type of the elements directly from "seq".

In the following F# code we have a base class A and two derived classes, B and C:

type A(x) =
    member a.X = x

type B(x) =
    inherit A(x)

type C(x) =
    inherit A(x)

If I try to "yield" their instances in a simple sequence expressions, I get two errors:

// Doesn't work, but it makes sense.
let testSeq = seq {
    yield A(0)
    yield B(1) // Error, expected type: A
    yield C(2) // Error, expected type: A
}

That can make sense, since it may not be so trivial to infer "common" types (interfaces, I think, can make that work far harder). However, those errors can be fixed with a safe cast:

// Works fine :)
let testSeqWithCast = seq {
    yield A(0)
    yield B(1) :> A
    yield C(2) :> A
}

What if I do not want to use casts? I tried to specify the sequence type directly from "seq", but things do not seem to work:

// Should work, I think...
let testGenSeq = seq<A> {
    yield A(0)
    yield B(1) // Error, expected type: A
    yield C(2)
}

So, my question is: is there a way to avoid casts? If not, is there a reason why even specifying the type doesn't make the code work?

I tried digging through following links:

http://msdn.microsoft.com/en-us/library/dd233209.aspx http://lorgonblog.wordpress.com/2009/10/25/overview-of-type-inference-in-f/

But I found nothing useful...

Thank you in advance for any kind of answer you can give :)


Solution

  • In order to understand the cause of your confusion you should not go anywhere further, than the first statement of the link you referred to :

    A sequence is a logical series of elements all of one type.

    You can return a sequence of only one, the same type like seq<A>, or seq<obj>. The OOP-ish fact that types B and C are inherited from A is not relevant. The following may help: all your instances are also inherited from obj, but in order to make from them a seq<obj> you should explicitly cast:

    // Works fine
    let testSeq = seq<obj> {
        yield A(0) :> obj
        yield B(1) :> obj
        yield C(2) :> obj
    }
    

    or just box them like below:

    // Works fine too
    let testSeq = seq {
        yield box (A(0))
        yield box (B(1))
        yield box (C(2))
    }
    

    EDIT: For understanding the reasoning behind explicit casting in F# the following (simplistic) consideration may help. Type inference does not do guessing; unless it can derive seq type deterministically, or have it explicitly declared, it will complain.

    If you just do

    let testSeq = seq {
       yield A(0)
       yield B(1)
       yield C(2)
    }
    

    compiler is presented with indeterminism - testSeq can be either seq<A>, or seq<obj>, so it complains. When you do

    let testSeq = seq {
       yield A(0)
       yield upcast B(1)
       yield upcast C(2)
    }
    

    it infers testSeq as seq<A> based on type of the first member and upcasts B and C to A without complaining. Similarly, if you do

    let testSeq = seq {
       yield box A(0)
       yield upcast B(1)
       yield upcast C(2)
    }
    

    it will infer testSeq as seq<obj> based on the type of the first member upcasting this time second and third members to obj, not A.