These four versions compile but i'm curious about context in which we should prefer one option rather than another.
// 1
def f[N, S <: Seq[N]](s: S)
// 2
def f[N, S[N] <: Seq[N]](s: S[N])
They are pretty similar when use 1
rather than 2
2
impose that S
have N
as generic parameter as 1
but then what is the difference between these two ?
Then we have more general settings.
// 3
def f[N, S[X] <: Seq[X]](s: S[N])
// 3.a
def f[N, S[X] <: Seq[X]](s: S[N]): S[Int]
From what i humbly understood 3
authorize to extract the generic container type to reuse it later and get something like 3.a
.
But what is the meaning of the undeclared X generic parameter, i suppose it's a way to declare something special but i don't get it.
// 4
def f[N, S[X] <: Seq[_]](s: S[N])
I don't know what to say about 4
except than from what i know Seq[_]
stands for Seq[Any]
Finally i'm simply curious to have more information about these tools and their specificity to get things done more properly.
// 2
def f[N, S[N] <: Seq[N]](s: S[N])
The idea here is that the first N
parameter and N
mentioned in S[N] <: Seq[N]
are completely independent parameters. They are just sharing the same name.
N
mentioned in S[N]
is visible only in a scope of its bound <: Seq[N]
.
N
used in parameter definition (s: S[N])
comes from first N
as this is the only N
parameter visible for parameter type definition. So instead of N
in S[N] <: Seq[N]
you can use any letter and this will not affect your parameter type at any way.
// 4
def f[N, S[X] <: Seq[_]](s: S[N])
Here you just ignored X
parameter.
Edit: as @alexey-romanov mentioned in a comment. There is difference between S[X] <: Seq[X]
and S[X] <: Seq[_]
Here is an example showing the difference:
def f1[N, S[X] <: Seq[X]](s: S[N]) = ""
def f2[N, S[X] <: Seq[_]](s: S[N]) = ""
type Foo[A] = Seq[Int]
val foo: Foo[String] = Seq(2,3)
//f1(foo) -- compilation error
f2(foo)
The problem here is as type constrictor is a kind of "function on types", we can define such "function" accepting one type as a parameter but returning type parametrized by another parameter not related to parameter used in type constructor. (See type Foo
)
Passing foo
val to f2
is fine because X
is infered to String and Foo[String]
is a "subtype"(actually they are equal) of Seq[Int]
, but when we pass foo
to f1
the X
is still a String
but Foo[String]
is not "subtype" of Seq[String]
(because Foo[String]==Seq[Int] not subtype of Seq[String])
// 1
def f[N, S <: Seq[N]](s: S)
And here you said that N
used in Seq[N]
is the same as first parameter N
. So this is the same N