file:grokking-monad/scala/en/part3.org
source link: https://blog.oyanglul.us/grokking-monad/scala/en/part3.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Table of Contents
This is the literal programming file that generates 4-1-kind.scala and 4-2-free.scala
you can actually run
sbt "testOnly *Kind"
andsbt "testOnly *Free"
for exercises
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.
FunctionK
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
}
Kind Projector
you'll notice that we've include a compiler plugin kind-projector
https://github.com/non/kind-projector
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\""
}
Rank N Type
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.
implicit def spherePrintable[A](implicit p: Printable[Box[A]],
fn: Sphere[A] => Box[A]): Printable[Sphere[A]] = ???
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)\""
}
Natural Transformation
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)
}
}
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK