Introduction to functional programming in Scala
Table of Contents
EitherT by Cats
EitherT[F, A, B] is a monad transformer for Either. It wraps a value of type F[Either[A, B]], where:
• F is an effect type (like Future, IO, etc.)
• A represents the error type (the left side)
• B represents the success type (the right side)
EitherT.leftT
leftT is a convenience method for creating an EitherT with a left value, without explicitly wrapping it in F. This is useful when you want to represent an error in the EitherT monad. For example, if you're working with an error value (e.g., a String), you can lift it into the Left side of the EitherT using leftT.
1import cats.data.EitherT
2import cats.implicits._
3import scala.concurrent.Future
4import scala.concurrent.ExecutionContext.Implicits.global
5
6val eitherTLeft: EitherT[Future, String, Int] = EitherT.leftT[Future, Int]("Error")
7// Equivalent to: EitherT(Future.successful(Left("Error")))EitherT.rightT
rightT works like leftT but lifts a value into the Right side of the EitherT. This is typically used to represent a successful result. For example, to lift a plain value like 42 into the Right side of an EitherT:
1import cats.data.EitherT
2import cats.implicits._
3
4val successResult: Int = 42
5
6// Lift the value into the Right side of EitherT
7val eitherTRight: EitherT[Option, String, Int] = EitherT.rightT[Option, String](successResult)
8
9// Now, eitherTRight is an EitherT[Option, String, Int] where the value is a RightEitherT.liftF
EitherT.liftF lifts a value of type F[A] into an EitherT[F, E, A]. This is useful when you already have a computation inside a monad like Future, Option, or IO, and you want to work with it in the EitherT context—allowing you to later add error handling using the Left side.
1import cats.data.EitherT
2import scala.concurrent.Future
3import scala.concurrent.ExecutionContext.Implicits.global
4import cats.implicits._
5
6val futureResult: Future[Int] = Future.successful(42)
7
8// Lift the Future into an EitherT[Future, String, Int]
9val eitherTFuture: EitherT[Future, String, Int] = EitherT.liftF(futureResult)
10
11// Now, eitherTFuture is an EitherT[Future, String, Int]This is a common pattern when integrating effectful computations with EitherT to take advantage of functional error handling.
OptionT by Cats
Difference between Option and OptionT
Option is a simple container that represents the presence or absence of a value. It is purely functional and does not represent computations or side effects—just optional data.
However, when working in functional effect systems like IO, Future, etc., you'll often encounter types like:
1val maybeUser: Option[User] = Some(user)
2val maybeUser: Option[User] = None
3
4// With effects (e.g., IO):
5val maybeUser: IO[Option[User]]In this scenario, you're working with a nested type: an outer effect (e.g., IO) and an inner container (Option). This results in F[Option[A]], which adds complexity—especially when composing logic across both layers.
Composing operations requires mapping and flatMapping through both the outer effect and the inner Option, which leads to verbose and harder-to-read code.
1val maybeUser: OptionT[IO, User]OptionT[F, A] is a Monad Transformer. It wraps F[Option[A]] and allows you to treat it like a single-layered monad. This abstraction simplifies your code by eliminating the need to manually unpack both F and Option.
Benefits of OptionT:
• You can map, flatMap, and use for-comprehensions directly.
• Abstracts over the boilerplate of dealing with nested monads like IO[Option[A]].
• Supports easy lifting and chaining of computations within an effectful context.