scalatypeclassammonite

Unable to recover typeclass instances for types with kind F[A1, A2] with syntax extensions


I've been playing around with the typeclass pattern in Scala to better understand how it works, as I'm familiar with Scalaz and wanted to figure out how it works "under the hood".

(You can run the following code with https://ammonite.io/ REPL)

import $plugin.$ivy.`org.spire-math::kind-projector:0.9.3`

sealed trait Maybe[+A] // Option
final case class Just[+A](value: A) extends Maybe[A]
final case object Null extends Maybe[Nothing]

sealed trait Direction[+E, +A] // Either
final case class Up[+E, +A](value: A) extends Direction[E, A]
final case class Down[+E, +A](value: E) extends Direction[E, A]

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

object FunctorSyntax {
  implicit final class FunctorExtensions[F[_], A](private val self: F[A]) extends AnyVal {
    def map[B](f: A => B)(implicit instance: Functor[F]): F[B] = {
      instance.map(self)(f)
    }
  }
}

object FunctorInstances {
  implicit val maybeFunctorInstance: Functor[Maybe] = new Functor[Maybe] {
    def map[A, B](fa: Maybe[A])(f: A => B): Maybe[B] = fa match {
      case Just(a) => Just(f(a))
      case n@Null => n
    }
  }

  implicit def directionFunctorInstance[E]: Functor[Direction[E, ?]] = new Functor[Direction[E, ?]] {
    def map[A, B](fa: Direction[E, A])(f: A => B): Direction[E, B] = fa match {
      case Up(a) => Up(f(a))
      case Down(e) => Down(e)
    }
  }
}

So I've written some analogues to Option (Maybe) and Either (Direction), the Functor definition, some instances for Functor, some some syntax extensions so I can call .map on valid functors.

The following code works:

import FunctorInstances._
import FunctorSyntax._

val x: Maybe[Int] = Just(5)

println(x.map(_ + 1)) // prints "Just(6)"

as expected. But the following does not:

val y: Direction[String, Int] = Up(5)

println(y.map(_ + 1)) // errors out

throwing the error help.sc:48: value map is not a member of ammonite.$file.help.Direction[String,Int]

Simply put, I do not want this error to happen, and for .map to work on an arbitrary Direction[E, ?].

I'm thinking that Scala isn't able to see that Direction[String, Int] can be destructured into a F = Direction[String, ?] and A = String, preventing the FunctorExtensions class from wrapping itself around the val y: Direction[String, Int]. Unfortunately, I do not know how to fix this.

NB: the instance itself is still recoverable with an implicitly

val instance = implicitly[Functor[Direction[String, ?]]]
println(instance.map(y)(_ + 1)) // prints "Up(6)"

Solution

  • You are probably missing the -Ypartial-unification scalac option if you are using Scala 2.11 or Scala 2.12.

    Add scalacOptions += "-Ypartial-unification" to your build or you could use Scala 2.13 which has it on by default.

    See https://github.com/typelevel/cats/blob/v1.6.1/README.md#getting-started