Skip to content

Instantly share code, notes, and snippets.

@tpolecat
Last active October 10, 2021 18:02
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tpolecat/67644320079cb7c5c2f5 to your computer and use it in GitHub Desktop.
Save tpolecat/67644320079cb7c5c2f5 to your computer and use it in GitHub Desktop.

EDIT: This gist has been promoted and is now a blog post.

Kinda-Curried Type Parameters

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:
 both value vectorInstance in trait VectorInstances of type => scalaz.Traverse[scalaz.Scalaz.generic.IxSq] with scalaz.MonadPlus[scalaz.Scalaz.generic.IxSq] with scalaz.Each[scalaz.Scalaz.generic.IxSq] with scalaz.Index[scalaz.Scalaz.generic.IxSq] with scalaz.Length[scalaz.Scalaz.generic.IxSq] with scalaz.Zip[scalaz.Scalaz.generic.IxSq] with scalaz.Unzip[scalaz.Scalaz.generic.IxSq] with scalaz.IsEmpty[scalaz.Scalaz.generic.IxSq] with scalaz.Align[scalaz.Scalaz.generic.IxSq]{def toIndexedSeq[A](fa: scalaz.Scalaz.generic.IxSq[A]): scalaz.Scalaz.generic.IxSq[A]}
 and value callableMonad in trait CallableInstances of type => scalaz.Monad[java.util.concurrent.Callable]
 match expected type scalaz.Applicative[F]
              wrap(1) : List[Int] // nope
                  ^

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 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 may all be obvious, dunno. It took me a few minutes to figure out and I did it twice in the last few weeks so I figured I would write it down. Let me know if there is a better approach.

@milessabin
Copy link

This is the best way to do it (though I wouldn't use the structural type, in this case it really is making a reflective call, and the language import/warning are ugly). There are multiple instances in shapeless and elsewhere, and I guess it qualifies as "folklore".

Definitely needed a write up: good job 👍 ...

@S11001001
Copy link

/cc @non, we were talking about this yesterday 👍

This approach is also used by scalaz.Tag.of.

@notxcain
Copy link

This one is being used a lot in play Enumeratee and co.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment