July 30, 2015

Kinda-Curried Type Parameters

This started off as a gist+tweet but got sufficient attention to warrant immortalization as a blog post. Thanks @milessabin, @S11001001 and others for confirming that it’s legit.

Scala methods can have multiple lists of value parameters but only one list of type parameters, which is occasionally irritating when some are inferable and others are not. Consider this method which has two type parameters, one inferable and one not.

import scalaz._, Scalaz._

def wrap[F[_]: Applicative, A](a: A): F[A] =
  Applicative[F].point(a)

It works fine, but you must supply both type arguments even though the second is inferable.

scala> wrap[List,Int](1) // ugh
res0: List[Int] = List(1)

scala> wrap(1) : List[Int] // nope
<console>:15: error: ambiguous implicit values:
 [one thousand lines of uesless information]

What we really want is to be able to say wrap[Option](3). But how do we write it?

The trick is to use an unconstrained no-arg method to bind the first (non-inferrable) type argument, constructing an instance of a class with a polymorphic apply method to bind the second (inferrable) type argument. Note that the typeclass constraint is pushed down to the second application. If there were no such constraint then a scalaz.Forall could replace WrapHelper.

final class WrapHelper[F[_]] {
  def apply[A](a: A)(implicit ev: Applicative[F]): F[A] =
    ev.point(a)
}

def wrap[F[_]] = new WrapHelper[F]

Which works as desired.

scala> wrap[List](1) // yay!
res1: List[Int] = List(1)

scala> wrap[Option](1)
res2: Option[Int] = Some(1)

This can also be done with a structural type, which is syntactically simpler but requires reflective access (which is not recommended … it can be slow and can cause problems with some bytecode manipulation tools).

import scala.language.reflectiveCalls

def wrap[F[_]] = new {
  def apply[A](a: A)(implicit ev: Applicative[F]): F[A] =
    ev.point(a)
}

This trick appears here and there in Scala libraries that take use fancy types, including shapeless and scalaz (and Play iteratees, I am told).