Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala higher kinded type variance

I am dipping my toes in higher kinded types, exploring a very basic Scala example:

trait Mappable[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

object Mappable {
  implicit object MappableOption extends Mappable[Option] {
    def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa.map(f)
  }
  implicit object MappableSeq extends Mappable[Seq] {
    def map[A, B](fa: Seq[A])(f: A => B): Seq[B] = fa.map(f)
  }
}

def bananaTuple[F[_], T](f: F[T])(implicit F: Mappable[F]): F[(String, T)] =
  F.map(f)(("banana", _))

This works:

bananaTuple(Option(42)) // Some((banana,42))
bananaTuple(Seq(42))    // List((banana,42))

But this does not compile:

bananaTuple(Some(42))
bananaTuple(List(42))

The compile errors I get:

could not find implicit value for parameter F: ch.netzwerg.hkt.HigherKindedTypes.Mappable[Some] bananaTuple(Some(42))

not enough arguments for method bananaTuple: (implicit F: ch.netzwerg.hkt.HigherKindedTypes.Mappable[Some])Some[(String, Int)]. Unspecified value parameter F. bananaTuple(Some(42))

How can I bring variance into the game?

like image 740
Rahel Lüthy Avatar asked Oct 18 '22 18:10

Rahel Lüthy


1 Answers

We can make this work with a little more parameteric polymorphism:

object MappableExample {
  trait Mappable[F[_]] {
    type Res[_]
    def map[A, B](f: A => B)(c: F[A]): Res[B]
  }

  implicit def seqMappable[C[X] <: Seq[X]] = new Mappable[C] {
    type Res[X] = Seq[X]
    override def map[A, B](f:A => B)(c: C[A]): Seq[B] = c.map(f)
  }

  implicit def optionMappable[C[X] <: Option[X]]: Mappable[C] = new Mappable[C] {
    type Res[X] = Option[X]
    override def map[A, B](f: A => B)(c: C[A]): Option[B] = c.map(f)
  }

  def map[A, B, C[_]](xs: C[A])(f: A => B)(implicit mappable: Mappable[C]): mappable.Res[B] = {
    mappable.map(f)(xs)
  }

  def main(args: Array[String]): Unit = {
    println(map(List(1,2,3))(("banana", _)))
    println(map(Some(1))(("banana", _)))
  }
}

Yields:

List((banana,1), (banana,2), (banana,3))
Some((banana,1))

The compiler now infers Some as Mappable[Some]#Res[Int] and Mappable[List]#Res[Int] which is quite ugly. One would expect the compiler to actually be able to infer the right type without needing for any co/contravariance on the Mappable trait, which we can't do since we're using it in an invariant position.

like image 151
Yuval Itzchakov Avatar answered Oct 20 '22 21:10

Yuval Itzchakov