package free
import monad._
import cats._
import org.scalatest._
class `4-1-Kind` extends AsyncFlatSpec with Matchers {
Kinds are types for types. Like a function for parameters of type and return a type.
e.g. List[_]
is a Kind, it has a hole
represented as _
in it, which is like parameter in function.
If you fill the hole with a type String
, then you will get a type List[String]
.
Recall our Printable[_]
kind defined in 3.1.Functor
, we’vecreatede implement of typeclass Contravariant
over kind Printable
, which means no matter what type you fill into the hole of Printable[_]
, it should
fulfill constrain of typeclass Contravariant
.
You can also define a function over Kind F[_] -> G[_]
, which is called FunctionK , just like function over type.
to define a FuntionK in cats, we can simply use fish arrow ~>
e.g. a FuntionK from List
to Option
import cats.~>
val first = new (List ~> Option) {
def apply[A](l:List[A]): Option[A] = l.headOption
}
you’ll notice that we’ve include a compiler plugin kind-projector=[fn:1] in =build.sbt
it provides us pretty syntactic suger for such case
val first = Lambda[List ~> Option](_.headOption)
which will be expanded to exactly the same code as what we defined before.
behavior of "FunctionK"
it should "create a FunctionK from Box[_] to Sphere[_]" in {
Printable.format(Sphere("hill")) shouldBe "\"hill\""
}
But why this is useful than function, the fnk
in spherePrintable
can be easily replace with a
simple function and it should behave still the same.
let’s create another function that use Sphere ~> Box
, to make tuple of sphere (Sphere[String], Sphere[Int])
printable
implicit def tuplePrintable[A, B, C](
implicit p: Printable[Box[String]],
fn: Sphere[C] => Box[C])): Printable[(Sphere[A], Sphere[B])] = {
val tupleOfSphereToBox = (tupleOfSphere: (Sphere[A], Sphere[B])) => {
val box1 = fn(tupleOfSphere._1)
val box2 = fn(tupleOfSphere._2)
Box(s"(${box1.value}, ${box2.value})")
}
Contravariant[Printable].contramap(p)(tupleOfSphereToBox)
}
you will get some compile error as such
[error] /Users/jichao.ouyang/Develop/scala-dojo/src/main/scala/Free.scala:21:35: type mismatch; [error] found : free.Sphere[A] [error] required: free.Sphere[C] [error] val box1 = fn(tupleOfSphere._1) [error] ^ [error] /Users/jichao.ouyang/Develop/scala-dojo/src/main/scala/Free.scala:22:35: type mismatch; [error] found : free.Sphere[B] [error] required: free.Sphere[C] [error] val box2 = fn(tupleOfSphere._2) [error] ^
Apparently when your fn
is sticked to a C
type, it’s not convertible to neither A nor B type, even if you stick it
to A
type, then the tupleOfSphere._2
can’t convert to A
either.
This is Rank 1 Type, because all types A, B, C are fixed in the same rank of tuplePrintable
’s polymorphism.
To make such code compile, you’ll need to make fn
as Rank 2 Type
, which means fn
will not be fixed in the first rank of tuplePrintable
polymorphism, it’s type polymorphism will be in another rank totally independent from the tuplePrintable
behavior of "Rank N Type"
it should "able to print a tuple of Sphere" in {
Printable.format((Sphere("hill"), Sphere(1))) shouldBe "\"(hill, 1)\""
}
If your kinds are happened to be a Functor, then this functionK becomes Natural Transformation
There’s nothing different except that Natural Transformation will provide you a property:
applying FunctionK before or after a Functor
map
makes no difference
hence fnk(fa.map(f))
is exactly the same as fnk(fa).map(f)
"Natural Transformation" should "satisfy law" in {
implicit val functorBox: Functor[Box] = new Functor[Box] {
def map[A, B](fa: Box[A])(f: A => B) =
Box(f(fa.value))
}
import cats.syntax.functor._
Sphere.sphereToBox(Sphere(100).map(_ + 1)) shouldBe Sphere.sphereToBox(Sphere(100)).map(_ + 1)
}
}