Search code examples
f#flexible-type

Why does the type of Seq.concat involve a flexible type?


I define two functions, one with and one without a flexible type:

let concatA = Seq.concat // ... : seq<#seq<'b>> -> seq<'b>
let concatB = concatA : seq<seq<'b> -> seq<'b>

Is there a context in which I can use one but not the other?

My understanding is that flexible types are helpful for functions f: #B -> #B, where the type guarantees that (f (x : D)) : D for any D derived from B. I don't see how the flexible type is helpful for Seq.concat?

(I chose Seq.concat as an example because its used in the documentation for Flexible Types uses it, but using concatB in that example still seems to type everything.)


Solution

  • It lets you call Seq.concat with a sequence of specific collections. For example:

    [ [1]; [2] ] |> concatA  // Works
    [ [1]; [2] ] |> concatB  // Does not work
    

    The argument is list<list<int>> which only implements the interface seq<list<int>>. This means that the second call does not work (because the argument requires seq<seq<int>>), but the first one does thanks to the flexible type - seq<#seq<int>> unifies with seq<list<int>>.

    A slightly confusing thing is that if F# knows the required type of a collection literal, it sometimes inserts conversions behind the scenes, so the following would actually work too (but only thanks to a hidden conversion):

    concatB [ [1]; [2] ]