Skip to content

Instantly share code, notes, and snippets.

@chrilves
Last active February 19, 2019 14:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrilves/85c959fa8227eaa4282338dd157bb8da to your computer and use it in GitHub Desktop.
Save chrilves/85c959fa8227eaa4282338dd157bb8da to your computer and use it in GitHub Desktop.
import scala.language.higherKinds
trait Functor[F[_]] {
def map[A,B](fa: F[A])(f: A => B): F[B]
}
/* Any functor is by definition covariant because:
Let A and B be two types such that A <: B, then
(fa: F[A]).map((a:A) => (a:S)) : F[S]
but also
(fa: F[A]).map((a:A) => (a:S)) == fa because `(a:A) => (a:S)`
is the identity function. So `fa : F[S]` too.
It happens often that functors are not declared as
covariant but invariant. For example take the following
definition:
*/
def acceptOnlyCovariant[F[+_]](x: F[Int]): F[Int] = {
println(s"Class: ${x.getClass}, value: $x")
x
}
/* This definition only accept covariant type constructors
F but we want to apply it on this functor:
*/
final case class Id[A](value: A)
implicit val Id_is_indeed_a_functor: Functor[Id] =
new Functor[Id] {
def map[A,B](fa: Id[A])(f: A => B): Id[B] = Id(f(fa.value))
}
val v0: Id[Int] = Id(0)
/* Applying acceptOnlyCovariant on v0 is not accepted by the
type checker:
scala> acceptOnlyCovariant(v0)
error: inferred kinds of the type arguments (Id) do not conform to the expected kinds of the type parameters (type F).
Id's type parameters do not match type F's expected parameters:
type A is invariant, but type _ is declared covariant
acceptOnlyCovariant(v0)
^
<console>:15: error: type mismatch;
found : Id[Int]
required: F[Int]
acceptOnlyCovariant(v0)
*/
/* To make acceptOnlyCovariant accept v0 as argument, we
will cheat! We will disguise `F` as a covariant functor!
*/
final class ForceCovariance[F[_]](val functorF: Functor[F]) extends AnyVal {
/* Note that `G` does not have a definition!
`G` will actually be F but Scala won't notice.
*/
type G[+ _]
/* `G` is actually `F` which means we can replace
`G` by `F` and `F` by `G` in any situation. */
@inline final def from[H[_[_]]](e: H[F]): H[G] =
e.asInstanceOf[H[G]]
@inline final def to[H[_[_]]](e: H[G]): H[F] =
e.asInstanceOf[H[F]]
/* Note that the only way to get a value whose type
contains G is to provide a value with F instead
so any value of type G[A] is actually of type F[A]
For example, to get an instance of `Functor[G]` we just have to do:
*/
@inline final def functorG: Functor[G] = from[Functor](functorF)
}
object ForceCovariance {
@inline final def apply[F[_]](implicit F: Functor[F]): ForceCovariance[F] =
new ForceCovariance[F](F)
}
def acceptInvariant[F[_]: Functor](arg: F[Int]): F[Int] = {
val cheat = ForceCovariance[F]
type H[L[_]] = L[Int]
// Is actually F[Int] in disguise!
val cheatedArg: cheat.G[Int] = cheat.from[H](arg)
// Now we can call `acceptOnlyCovariant`
val cheatedRes: cheat.G[Int] = acceptOnlyCovariant[cheat.G](cheatedArg)
// But we want to return a F[Int], not cheat.G[Int]!
cheat.to[H](cheatedRes)
}
// And voila!
acceptInvariant(v0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment