Last active
February 19, 2019 14:59
-
-
Save chrilves/85c959fa8227eaa4282338dd157bb8da to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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