So, an underscore and a generic type can sometimes overlap or be used in the same spot, but they mean pretty different things. Underscore pretty much just means I’m too lazy to write out the type or perhaps figure out what the type is supposed to be, and would rather the compiler did that for me. A generic type means that when invoking the function, any type should suffice (or perhaps any type that satisfies some constraints). You already pointed out an example where you can use an underscore or a generic type interchangeably. An example where you can use an underscore but not a generic type would be
let matches : Customer -> CustomerContactInfo = ...
let lookupContact (contacts:IDictionary<_,_>) (employee:Employee) customer =
match contacts.TryGetValue employee.ID with
| (true, contacts) -> contacts |> Array.tryFind (matches customer)
| (false, _) -> None
So there I could annotate contacts as IDictionary<EmployeeID, CustomerContactInfo[]>, but that’s a lot of typing. I really just need to tell the compiler that it’s an IDictionary, and it can figure out the rest. But it wouldn’t be correct if I said IDictionary<'a, 'b>, because the key and value types have to match specific concrete types, not just anything.
The compiler always tries to infer the most generic type possible, so it just so happens that an underscore can often end up being the same as just a generic type, but they do technically say different things.
If you are writing an annotation and intend the input to truly be generic, I recommend sticking with 'a instead of _, because the compiler will tell you if you accidentally do something to make it no longer generic, e.g.,
> let f (x:'a) =
- if x = 5 then true else false;;
if x = 5 then true else false;;
---------^
stdin(11,10): warning FS0064: This construct causes code to be less generic than indicated by the type annotations. The type variable 'a has been constrained to be type 'int'.
val f: x: int -> bool
Use the underscore when you really intend to say, “I don’t care to bother with what type this is, and want the compiler to just figure it out.” Of course you don’t need the annotation at all if the whole thing is just an underscore, so you often see it when you need to tell the compiler part of an annotation but don’t want to type out the whole thing.