Data types that have instances of Tell[F, L] are able to accumulate values of type L in the F[_] context. This is done by adding values of L to the accumulated log by using the tell function:

trait Tell[F[_], L] {
  def tell(l: L): F[Unit]

This log is write-only and the only way to modify it is to add more L values. Usually most instances require a Monoid[L] in order to properly accumulate values of L.

Let’s have a look at an example how you could Tell to log service calls. First we’ll add the actual service call with its parameters and result types:

import cats._
import cats.implicits._
import cats.mtl.Tell

case class ServiceParams(option1: String, option2: Int)

case class ServiceResult(userId: Int, companies: List[String])  

def serviceCall[F[_]: Monad](params: ServiceParams): F[ServiceResult] = 
  // a fake call to some external service, replace with real implementation
  ServiceResult(0, List("Raven Enterprises")).pure[F]

Now let’s write an extra function that log what kind of parameters were passed as well as what the service returned. For the log, we’ll be using a Chain[String], as it supports very efficient concatenation:

def serviceCallWithLog[F[_]: Monad](params: ServiceParams)(implicit F: Tell[F, Chain[String]]): F[ServiceResult] =
  for {
    _ <- F.tell("Call to service with ${params.option1} and ${params.option2}"))
    result <- serviceCall[F](params)
    _ <- F.tell("Service returned: userId: ${result.userId}; companies: ${result.companies}"))
  } yield result

Now let’s materialize this to see if it worked. Instances for Tell include Writer and WriterT, but also Tuple2:

val (log, result): (Chain[String], ServiceResult) =
  serviceCallWithLog[Writer[Chain[String], ?]](ServiceParams("business", 42)).run
// log: Chain[String] = Append(
//   leftNE = Singleton(a = "Call to service with business and 42"),
//   rightNE = Singleton(
//     a = "Service returned: userId: 0; companies: List(Raven Enterprises)"
//   )
// )
// result: ServiceResult = ServiceResult(
//   userId = 0,
//   companies = List("Raven Enterprises")
// )

And voila, it works as expected. We were using just a standard Writer for this example, but remember that any monad transformer stack with WriterT in it somewhere will have an instance of Tell.