Local

Local[F, E] extends Ask and allows to us express local modifications of the environment. In practice, this means, that whenever we use local on a value of ask, we can locally modify the environment with a function E => E. In this case, locally modifying means that the only place we can actually observe the modification is in the F[A] value passed to the local function.

The local function has the following signature:

trait Local[F[_], E] extends Ask[F, E] {
  def local(fa: F[A])(f: E => E): F[A]
}

Here's a quick example of how it works:

import cats._
import cats.data._
import cats.implicits._
import cats.mtl._

def calculateContentLength[F[_]: Applicative](implicit F: Ask[F, String]): F[Int] =
  F.ask.map(_.length)
  
def calculateModifiedContentLength[F[_]: Applicative](implicit F: Local[F, String]): F[Int] =
  F.local(calculateContentLength[F])("Prefix " + _)

val result = calculateModifiedContentLength[Reader[String, *]].run("Hello")
// result: Int = 12

We can see that the content length returned is 12, because the string "Prefix " is prepended to the environment value. To see that local only applies to the passed parameter we can run both calculateContentLength and calculateModifiedContentLength in one for-expression:

def both[F[_]: Monad](implicit F: Local[F, String]): F[(Int, Int)] = for {
  length <- calculateContentLength[F]
  modifiedLength <- calculateModifiedContentLength[F]
} yield (length, modifiedLength)

val res = both[Reader[String, *]].run("Hello")
// res: (Int, Int) = (5, 12)