Chapter 10

  1. Why does the type of monad transformer reflect the type of the stack "upside-down"?

It is impossible to define a monad composition in general, only in a way specific to the internal effect of the stack. Because of this, the name of the effect is fixed in the name of the transformer and the outer effect becomes a type parameter.

  1. Why is it possible to reuse existing monads for the top layer of the stack?

The return type of the Kleisli arrow fits well with the type of the stack. For this reason, it is possible to produce the result of the proper type by utilizing the flatMap method of the outer monad.

  1. Why is it impossible to reuse existing monads for the bottom layer of the stack?

The argument type of the arrow expects a plain argument. Consequently, we need to extract the effect-free value from the context of internal effect. This is only possible in a specific way but not in general. 

  1. Implement the TryT monad transformer.
private def noResultTryT[F[_] : Monad, T](ex: Throwable): F[Try[T]] = 
Monad[F].unit(Failure[T](ex))

implicit class TryT[F[_] : Monad, A](val value: F[Try[A]]) {
def compose[B](f: A => TryT[F, B]): TryT[F, B] = {
val result = value.flatMap {
case Failure(ex) => noResultTryT[F, B](ex)
case Success(a) => f(a).value
}
new TryT(result)
}

def isSuccess: F[Boolean] = Monad[F].map(value)(_.isSuccess)
}

def tryTunit[F[_] : Monad, A](a: => A) = new TryT(Monad[F].unit(Try(a)))

implicit def TryTMonad[F[_] : Monad]: Monad[TryT[F, ?]] = new Monad[TryT[F, ?]] {
override def unit[A](a: => A): TryT[F, A] = Monad[F].unit(Monad[Try].unit(a))
override def flatMap[A, B](a: TryT[F, A])(f: A => TryT[F, B]): TryT[F, B] = a.compose(f)
}
  1. Use the TryT monad transformer instead of EitherT with the example functions from this chapter.
object Ch10FutureTryFishing extends FishingApi[TryT[Future, ?]] with App {
val buyBateImpl: String => Future[Bate] = ???
val castLineImpl: Bate => Try[Line] = ???
val hookFishImpl: Line => Future[Fish] = ???

override val buyBate: String => TryT[Future, Bate] =
(name: String) => buyBateImpl(name).map(Try(_))
override val castLine: Bate => TryT[Future, Line] =
castLineImpl.andThen(Future.successful(_))
override val hookFish: Line => TryT[Future, Fish] =
(line: Line) => hookFishImpl(line).map(Try(_))

val result: Future[Try[Fish]] = goFishing(tryTunit[Future, String]("Crankbait")).value
}
  1. Implement another take on the monad transformer stack, this time with this layers placed upside-down: EitherT[OptionT[Future, A], String, A].
type Inner[A] = OptionT[Future, A]
type Outer[F[_], A] = EitherT[F, String, A]
type Stack[A] = Outer[Inner, A]

object Ch10EitherTOptionTFutureFishing extends FishingApi[Stack[?]] with App {

val buyBateImpl: String => Future[Bate] = ???
val castLineImpl: Bate => Either[String, Line] = ???
val hookFishImpl: Line => Future[Fish] = ???

override val castLine: Bate => Stack[Line] =
(bate: Bate) => new OptionT(Future.successful(Option(castLineImpl(bate))))

override val buyBate: String => Stack[Bate] =
(name: String) => new OptionT(buyBateImpl(name).map(l => Option(Right(l)): Option[Either[String, Bate]]))

override val hookFish: Line => Stack[Fish] =
(line: Line) => new OptionT(hookFishImpl(line).map(l => Option(Right(l)): Option[Either[String, Fish]]))

val input: EitherT[Inner, String, String] = eitherTunit[Inner, String, String]("Crankbait")
val outerResult: Inner[Either[String, Fish]] = goFishing(input).value
val innerResult: Future[Option[Either[String, Fish]]] = outerResult.value

}
  1. Add an action to release the caught fish to the free monad example we developed in the chapter.

Only the changed parts of the example are shown here. Please see the accompanying code to see the example with incorporated changes:

final case class ReleaseFish[A](fish: Fish, f: Unit => A) extends Action[A]

def releaseFish(fish: Fish): Free[Action, Unit] = Join(ReleaseFish(fish, _ => Done(())))

implicit val actionFunctor: Functor[Action] = new Functor[Action] {
override def map[A, B](in: Action[A])(f: A => B): Action[B] = in match {
... // other actions as before
case ReleaseFish(fish, a) => ReleaseFish(fish, x => f(a(x)))
}
}

def catchFish(bateName: String): Free[Action, _] = for {
bate <- buyBate(bateName)
line <- castLine(bate)
fish <- hookFish(line)
_ <- releaseFish(fish)
} yield ()

def goFishingLogging[A](actions: Free[Action, A], unit: Unit): A = actions match {
... // the rest as in the chapter code
case Join(ReleaseFish(fish, f)) =>
goFishingLogging(f(()), log(s"Releasing the fish $fish"))
}

def goFishingAcc[A](actions: Free[Action, A], log: List[AnyVal]): List[AnyVal] = actions match {
...
// the rest as in the chapter code
case Join(ReleaseFish(fish, f)) =>
goFishingAcc(f(()), fish.copy(name = fish.name + " released") :: log)
}

We need to extend the action model and a function, add a helper lifting method, add the additional step to the definition of the process, and augment both interpreters to support the new action.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.14.132