WithContext
Installation
Section titled “Installation”or as a standalone dependency
(replace suffix *
with ce2
or ce3
depends on which cats-effect version you use)
What if you don’t need Env
Section titled “What if you don’t need Env”Env is a powerful monad, but what if you’re sure that you don’t need it?
You can still use convenient Tofu concepts to work with your own Environment (WithContext
).
Usage example and a short use case description
Section titled “Usage example and a short use case description”The short story long, it is possible to use ReaderT
:
import cats._import cats.data.ReaderTimport cats.instances.option._import tofu._import tofu.optics._
// defining our own Env that stores some Usercase class User(id: Int, name: String)case class MyEnv(user: User)
// defining an extractor, extractor is a common lens that you can read about// in a paragraph about lensesimplicit val extractor: Extract[MyEnv, User] = _.user
def program[F[_]: WithContext[*[_], MyEnv]](implicit u: MyEnv Extract User): F[String] = WithContext[F, MyEnv].extract(u).ask(_.name)
// ~voilàprogram[ReaderT[Option, MyEnv, *]].run(MyEnv(User(0, "Tofu"))) //> Some(Tofu): Option[String]
A bit more complicated example, that shows lenses usage only in the functions that require them:
import cats._import cats.data.ReaderTimport cats.instances.option._import cats.syntax.apply._import tofu._import tofu.optics._
// defining our own Env that stores a User and some related Metadatacase class User(id: Int, name: String)case class Metadata(height: Double, age: Int)case class MyEnv(user: User, md: Metadata)
// defining extractorsimplicit val userExtractor: Extract[MyEnv, User] = _.userimplicit val mdExtractor: Extract[MyEnv, Metadata] = _.md
// it is possible to define a program that only has a contextdef program[F[_]: Apply: WithContext[*[_], MyEnv]]: F[String] = (name[F], age[F]).mapN { (name, age) => s"$name: $age" }
// but all the functions that were called inside a program// have on demand and only necessary extractorsdef name[F[_]: WithContext[*[_], MyEnv]](implicit u: MyEnv Extract User): F[String] = WithContext[F, MyEnv].extract(u).ask(_.name)
def age[F[_]: WithContext[*[_], MyEnv]](implicit m: MyEnv Extract Metadata): F[Int] = WithContext[F, MyEnv].extract(m).ask(_.age)
// ~voilàprogram[ReaderT[Option, MyEnv, *]] .run(MyEnv(User(0, "Tofu"), Metadata(60, 18))) //> Some(Tofu: 18): Option[String]
It is also possible to do define some WithContext
explicitly without having a need in Env
or ReaderT
monads:
import cats._import cats.instances.option._import cats.syntax.apply._import tofu._import tofu.optics._
// defining our own Env that stores a User and some related Metadatacase class User(id: Int, name: String)case class Metadata(height: Double, age: Int)case class MyEnv(user: User, md: Metadata)
// defining extractorsimplicit val userExtractor: Extract[MyEnv, User] = _.userimplicit val mdExtractor: Extract[MyEnv, Metadata] = _.md
// what if we don't need or don't know what ReaderT is// we can define a const Context thanimplicit val ctx: WithContext[Option, MyEnv] = WithContext.const[Option, MyEnv](MyEnv(User(0, "Tofu"), Metadata(60, 18)))
// it is still possible to define a program that only has a contextdef program[F[_]: Apply: WithContext[*[_], MyEnv]]: F[String] = (name[F], age[F]).mapN { (name, age) => s"$name: $age" }
// and all the functions that were called inside a program// have on demand and only necessary extractorsdef name[F[_]: WithContext[*[_], MyEnv]](implicit u: MyEnv Extract User): F[String] = WithContext[F, MyEnv].extract(u).ask(_.name)
def age[F[_]: WithContext[*[_], MyEnv]](implicit m: MyEnv Extract Metadata): F[Int] = WithContext[F, MyEnv].extract(m).ask(_.age)
// ~voilàprogram[Option] //> Some(Tofu: 18): Option[String]