Search code examples
scalagenericshigher-kinded-types

Scala Generics, which of these 4 generic version is specific to which particular problem


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.


Solution

  • // 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