Lachlan
Lachlan

Reputation: 3784

Is is possible to improve type inference for partially applied types in Scala?

I'm trying to improve the type inference of the traverse_ function in the code below:

import scala.language.higherKinds

trait Applicative[AF[_]] {

  def ap[A, B](a: AF[A])(f: AF[A => B]): AF[B]

  def pure[A](a: A): AF[A]

  def fmap[A, B](a: AF[A])(f: A => B): AF[B]

}

def traverse_[AP[_]: Applicative, A](xs: Iterable[A])(f: A => AP[Unit]): AP[Unit] = {
  val ap = implicitly[Applicative[AP]]
  (xs :\ ap.pure(())) { (x, acc) =>
    val apFunc = ap.fmap(f(x))(a => identity[Unit] _)
    ap.ap(acc)(apFunc)
  }
}

implicit def optionAp = new Applicative[Option] {

  def ap[A, B](a: Option[A])(f: Option[A => B]): Option[B] = f flatMap (a map _)

  def pure[A](a: A) = Some(a)

  def fmap[A, B](a: Option[A])(f: A => B) = a map f

}

implicit def eitherAp[L] = new Applicative[({type l[x]=Either[L, x]})#l] {

  def ap[A, B](a: Either[L, A])(f: Either[L, A => B]): Either[L, B] = f.right flatMap (a.right map _)

  def pure[A](a: A) = Right(a)

  def fmap[A, B](a: Either[L, A])(f: A => B) = a.right map f

}

// silly, but compiles
val x = traverse_(1 to 10) {
  case 5 => None
  case _ => Some(())
}
println(x)

// also silly, but does not compile
val y = traverse_(1 to 10) {
  case 5 => Left("x")
  case _ => Right(())
}
println(y)

Running the above gives:

/Users/lodea/tmp/traverse.scala:49: error: no type parameters for method traverse_: (f: Int => AP[Unit])(implicit evidence$1: this.Applicative[AP])AP[Unit] exist so that it can be applied to arguments (Int => Product with Serializable with scala.util.Either[String,Unit])
 --- because ---
argument expression's type is not compatible with formal parameter type;
 found   : Int => Product with Serializable with scala.util.Either[String,Unit]
 required: Int => ?AP

val y = traverse_(1 to 10) {
                 ^
/Users/lodea/tmp/traverse.scala:49: error: type mismatch;
 found   : Int => Product with Serializable with scala.util.Either[String,Unit]
 required: Int => AP[Unit]
val y = traverse_(1 to 10) {
                           ^
two errors found

To get it to compile, I have to specify the type arguments to traverse_:

val y = traverse_[({type l[x]=Either[String, x]})#l, Int](1 to 10) {
  case 5 => Left("x")
  case _ => Right(())
}

Is there a way I can restructure traverse_, or any other part of the code, to make the type inference work? When the types start getting more complex, this gets annoying fast.

Upvotes: 8

Views: 946

Answers (1)

George
George

Reputation: 8378

As pointed out by Ben James, you are looking for Miles Sabin's Unapply trick. Here it is in scalaz repo. Here's traverseU, implemented with its help. Here are some example usages. And here's my sketchy (hopefully correct) implementation for your particular case (note: I've renamed your Applicative to ApplicativeTest not to interfere with Applicative, defined in scalaz):

scalaz> core/console
[warn] Credentials file /home/folone/.ivy2/.credentials does not exist
[info] Starting scala interpreter...
[info] 
Welcome to Scala version 2.9.2 (OpenJDK 64-Bit Server VM, Java 1.7.0_15).
Type in expressions to have them evaluated.
Type :help for more information.

scala> :paste
// Entering paste mode (ctrl-D to finish)

import scalaz._

trait ApplicativeTest[AF[_]] {
  def ap[A, B](a: AF[A])(f: AF[A => B]): AF[B]
  def pure[A](a: A): AF[A]
  def fmap[A, B](a: AF[A])(f: A => B): AF[B]
}

def traverse_[AP, A](xs: Iterable[A])(f: A => AP)(implicit G: Unapply[ApplicativeTest, AP]): G.M[Unit] = {
  (xs :\ G.TC.pure(())) { (x, acc) =>
    val apFunc = G.TC.fmap(G(f(x)))(a => identity[Unit] _)
    G.TC.ap(acc)(apFunc)
  }
}

implicit def optionAp = new ApplicativeTest[Option] {
  def ap[A, B](a: Option[A])(f: Option[A => B]): Option[B] = f flatMap (a map _)
  def pure[A](a: A) = Some(a)
  def fmap[A, B](a: Option[A])(f: A => B) = a map f
}

implicit def eitherAp[L]: ApplicativeTest[({type l[x]=Either[L, x]})#l] =
  new ApplicativeTest[({type l[x]=Either[L, x]})#l] {
    def ap[A, B](a: Either[L, A])(f: Either[L, A => B]): Either[L, B] = f.right flatMap (a.right map _)
    def pure[A](a: A) = Right(a)
    def fmap[A, B](a: Either[L, A])(f: A => B) = a.right map f
  }

implicit def iterAp = new ApplicativeTest[Iterable] {
  def ap[A, B](a: Iterable[A])(f: Iterable[A ⇒ B]): Iterable[B] = f flatMap(a map _)
  def pure[A](a: A) = Iterable(a)
  def fmap[A, B](a: Iterable[A])(f: A ⇒ B) = a map f
}

// Exiting paste mode, now interpreting.

import scalaz._
defined trait ApplicativeTest
traverse_: [AP, A](xs: Iterable[A])(f: A => AP)(implicit G: scalaz.Unapply[ApplicativeTest,AP])G.M[Unit]
optionAp: java.lang.Object with ApplicativeTest[Option]{def pure[A](a: A): Some[A]}
eitherAp: [L]=> ApplicativeTest[[x]Either[L,x]]
iterAp: java.lang.Object with ApplicativeTest[Iterable]

scala> val x = traverse_(1 to 10) {
     |   case 5 => None
     |   case _ => Some(())
     | }
x: Option[Unit] = None

scala> val y = traverse_(1 to 10) {
     |   case 5 => Left("x"): Either[String, Unit]
     |   case _ => Right(())
     | }
y: Either[String,Unit] = Left(x)

I still don't know how to make it infer Either[String, Unit] instead of Product with Serializable with scala.util.Either[String,Unit] other than strictly specify type in one of the cases like I did on this line: case 5 => Left("x"): Either[String, Unit].

Upvotes: 9

Related Questions