This question is about _ as used in type constructor and not when used in defining existential types.
So the question is what is the difference when _ is us
The only difference I can think of is that with
F[_]the parameter itself can have as many holes as possible...that isF[_]can becomeF[Int]orF[Future[Option[Int]]]etc...while when you haveF[T]theTcan only be a proper type...that isF[String]orF[Int]etc.Is this a correct assumption?
Note that Future[Option[Int]] is a proper type of the same kind as Int or String. We can convince ourself by using :kind command in Scala REPL
scala> :kind -v Future[Option[Int]]
scala.concurrent.Future[Option[Int]]'s kind is A
*
This is a proper type.
scala> :kind -v Int
Int's kind is A
*
This is a proper type.
To drive the point, consider the following complicated-looking type
Function3[Int, Tuple2[Double, List[Int]], Char, Future[Either[String, Int]]]
It is still just a simple concrete * type
scala> :kind -v Function3[Int, Tuple2[Double, List[Int]], Char, Future[Either[String, Int]]]
(Int, (Double, List[Int]), Char) => scala.concurrent.Future[Either[String,Int]]'s kind is A
*
This is a proper type.
Hence we see the shape of Future[Option[Int]] is simply *, and F[_] does not need any extra "holes" to fit it. Both F[_] and F[T] type constructors take a type argument of exactly same shape, namely *, no more, nor less. For example, let us try to fit in more than it can handle
trait Bar[F[_]] // Bar is type constructor of higher order kind - shape (* -> *) -> *
def g[F[_]] = println("g takes type constructor type argument of * -> * shape")
scala> g[Bar]
^
error: kinds of the type arguments (Bar) do not conform to the expected kinds of the type parameters (type F).
Bar's type parameters do not match type F's expected parameters:
type F has 1 type parameter, but type _ has 0
This errors because actual (* -> *) -> * shape of Bar does not fit expected * -> * shape of F.