Since no one else has answered the question, I think I'll give it a go myself. I'm going to have to get a bit philosophical.
Generic programming is all about abstracting over similar types, without the loss of type information (which is what happens with object-oriented value polymorphism). In order to do this, the types must necessarily share some sort of interface (a set of operations, not the OO term) that you can use.
In object-oriented languages, types satisfy an interface by virtue of classes. Each class has its own interface, defined as part of its type. Since all classes
List<T> share the same interface, you can write code that works no matter which
T you choose. Another way to impose an interface is an inheritance constraint, and although the two seem different, they are sort of similar if you think about it.
In most object-oriented languages,
List<> is not a proper type in itself. It has no methods, and thus has no interface. It is only
List<T> that has these things. Essentially, in more technical terms, the only types you can meaningfully abstract over are those with the kind
*. In order to make use of higher-kinded types in an object-oriented world, you have to phrase type constraints in a manner consistent with this restriction.
For example, as mentioned in the comments, we can view
List<> as "mappable", in the sense that if you have a function, you could convert an
Option<T> into an
Option<S>, or a
List<T> into a
List<S>. Remembering that classes cannot be used to abstract over higher-kinded types directly, we instead make an interface:
IMappable<K<_>, T> where K<T> : IMappable<K<_>, T>
And then we implement the interface in both
IMappable<List<_>, T> and
IMappable<Option<_>, T> respectively. What we've done, is using higher-kinded types to place constraints on the actual (non-higher-kinded) types
List<T>. This is how it's done in Scala, though of course Scala has features such as traits, type variables, and implicit parameters that make it more expressive.
In other languages, it is possible to abstract over higher-kinded types directly. In Haskell, one of the highest authorities on type systems, we can phrase a type class for any type, even if it has a higher kind. For example,
class Mappable mp where
map :: mp a -> mp b
This is a constraint placed directly on an (unspecified) type
mp which takes one type parameter, and requires it be associated with the function
map that turns an
mp<a> into an
mp<b>. We can then write functions that constrain higher-kinded types by
Mappable just like in object-oriented languages you could place an inheritance constraint. Well, sort of.
To sum things up, your ability to make use of higher-kinded types depends on your ability to constrain them or to use them as part of type constraints.