scalaadvanced
Cats Monad Transformers
Use Cats monad transformers: EitherT, OptionT, and StateT for composable effect stacks.
scalaPress ⌘/Ctrl + Shift + C to copy
import cats.*
import cats.data.*
import cats.syntax.all.*
import cats.effect.IO
// Problem: nesting Future[Either[Error, Option[T]]] gets ugly
// Solution: Monad transformers
type Result[A] = EitherT[IO, String, A]
case class User(id: Int, name: String, email: String)
case class Order(id: Int, userId: Int, total: Double)
def findUser(id: Int): Result[User] =
if id > 0 then EitherT.rightT(User(id, s"User$id", s"user$id@test.com"))
else EitherT.leftT(s"Invalid user id: $id")
def findOrders(userId: Int): Result[List[Order]] =
EitherT.rightT(List(
Order(1, userId, 99.99),
Order(2, userId, 149.50)
))
def calculateDiscount(total: Double): Result[Double] =
if total > 200 then EitherT.rightT(total * 0.1)
else if total > 100 then EitherT.rightT(total * 0.05)
else EitherT.rightT(0.0)
// Composed pipeline
def userDiscount(userId: Int): Result[String] =
for
user <- findUser(userId)
orders <- findOrders(user.id)
total = orders.map(_.total).sum
discount <- calculateDiscount(total)
yield f"${user.name}: total=$$${total}%.2f, discount=$$${discount}%.2f"
// OptionT example
type MaybeIO[A] = OptionT[IO, A]
def findConfig(key: String): MaybeIO[String] =
val configs = Map("host" -> "localhost", "port" -> "8080")
OptionT.fromOption[IO](configs.get(key))
def parsePort(s: String): MaybeIO[Int] =
OptionT.fromOption[IO](s.toIntOption)
def serverAddress: MaybeIO[String] =
for
host <- findConfig("host")
portStr <- findConfig("port")
port <- parsePort(portStr)
yield s"$host:$port"
// StateT example
type GameState[A] = StateT[IO, Int, A] // Int is the score
def addPoints(points: Int): GameState[Unit] =
StateT.modify(score => score + points)
def getScore: GameState[Int] =
StateT.get
def gameRound(correct: Boolean): GameState[String] =
for
_ <- if correct then addPoints(10) else addPoints(-5)
score <- getScore
yield s"Score: $score"
// Validated (not a monad, but accumulates errors)
case class FormData(name: String, age: Int, email: String)
def validateName(s: String): ValidatedNel[String, String] =
if s.nonEmpty then s.validNel
else "Name is required".invalidNel
def validateAge(s: String): ValidatedNel[String, Int] =
s.toIntOption.filter(a => a >= 0 && a <= 150)
.map(_.validNel)
.getOrElse("Invalid age".invalidNel)
def validateEmail(s: String): ValidatedNel[String, String] =
if s.contains("@") then s.validNel
else "Invalid email".invalidNel
def validateForm(name: String, age: String, email: String): ValidatedNel[String, FormData] =
(validateName(name), validateAge(age), validateEmail(email)).mapN(FormData.apply)
object App extends cats.effect.IOApp.Simple:
def run: IO[Unit] = for
result <- userDiscount(1).value
_ <- IO.println(s"User discount: $result")
error <- userDiscount(-1).value
_ <- IO.println(s"Error case: $error")
addr <- serverAddress.value
_ <- IO.println(s"Server: $addr")
// Validated accumulates ALL errors
_ <- IO.println(validateForm("Alice", "30", "alice@test.com"))
_ <- IO.println(validateForm("", "abc", "no-at")) // 3 errors!
yield ()Use Cases
- Composable error handling stacks
- Optional value chaining
- Form validation with error accumulation
Tags
Related Snippets
Similar patterns you can reuse in the same workflow.
scalabeginner
Collections Map Filter Fold Operations
Master Scala collections: map, flatMap, filter, fold, groupBy, partition, and zip operations.
Best for: Data transformation and aggregation
#scala#collections
scalaintermediate
For-Comprehensions and Monadic Composition
Use for-comprehensions with Option, Either, Future, and custom monads for elegant composition.
Best for: Chaining optional computations
#scala#for-comprehension
scalaadvanced
Type Class Pattern Implementation
Implement the type class pattern in Scala 3: define, provide instances, and use with extension methods.
Best for: Ad-hoc polymorphism without inheritance
#scala#type-class
scalaadvanced
Cats Effect IO Monad Basics
Use Cats Effect IO for pure functional effects: sequencing, error handling, resource management.
Best for: Pure functional effect management
#scala#cats-effect