Search code examples
scalascala-cats

Why it is possible to pass not higher kinded type?


I am reading the book https://underscore.io/books/scala-with-cats/ and trying to understand contravariant in Cats.
On the page 68 there is an example of the implementation of Contravariant in Cats

We can summon instances of Contravariant using the Contravariant.apply method. Cats provides instances for data types that consume parameters, including Eq , Show , and Function1 . Here’s an example:

import cats.Contravariant
import cats.Show
import cats.instances.string._

object ContraCats {

  val showString = Show[String]

  val showSymbol = Contravariant[Show]
    .contramap(showString)((sym: Symbol) => s"'${sym.name}'")

  def main(args: Array[String]): Unit = {
    println(showSymbol.show('dave))
  }

}

Show is a typeclass not a type, so how it is possible to create an instance of typeclass Contravariant of the typeclass Show? And Show is not a higher kinded type.


Solution

  • The typeclass Show is declared as Show[T].

    Contravariant is declared as Contravariant[F[_]]

    If we say that

    • Int has kind *
    • List[Int] has kind *
    • List has kind * -> *
    • Monad declared as trait Monad[M[_]] has kind (* -> *) -> *

    then by analogy:

    • Show has kind * -> *
    • Show[Int] has kind *
    • Contravariant has kind (* -> *) -> *
    • Contravariant[Show] has kind *

    That is, Contravariant is a thing that takes * -> * and produces something of kind *. Since Show is of kind * -> *, the type Contravariant[Show] is valid.

    Now, in your code snippet, there is an expression Contravariant[Show]. It's not a type, and it should not be confused with the application of a type constructor. Desugared, this thing is essentially

    Contravariant.apply[Show](inst)
    

    where Contravariant is the companion object of trait Contravariant and inst is an implicitly supplied instance of type Contravariant[Show]. Again, everything fits together seamlessly.

    Remark on nomenclature. I would say that Show is a "higher kind". It's in no way definitive, but I like the following quote from the Atlassian Blog [emphasis and code indentation mine]:

    Furthermore, you can have kinds that are themselves parameterized by higher kinded types. So, something could not only take a type, but take something that itself takes type parameters. An example would be the covariant functor: Functor[F[_]], it has the kind:

    ((* -> *) -> *)
    

    This says: given a simple higher kinded type, produce the final type. For instance given a type constructor like List produce the final type Functor[List].

    From the above quote it is evident that it is at least common to refer to the kind * -> * of List as "higher kind". It's definitely higher than *. It's definitely higher than anything you can write down in current version of Java (if you write List in Java, the best you can get is a "blah blah the generic type List requires type parameters blah blah"-error).