Reading — step 1 of 7
Learn
FP in Scala devotes multiple chapters to monads. The Scala standard library uses them everywhere: Option, List, Try, Either, Future. Understanding the abstraction unlocks why for-comprehensions work universally.
What's a monad?
A monad is a type M[A] with two operations:
unit(orpure/apply):A => M[A]— wrap a valueflatMap:M[A] => (A => M[B]) => M[B]— chain operations
Plus laws: identity, associativity. The names matter less than the SHAPE.
// Option as a monad:
Some(5) // unit (via apply)
Some(5).flatMap(n => Some(n * 2)) // flatMap
// List as a monad:
List(1, 2, 3).flatMap(n => List(n, n * 10))
// List(1, 10, 2, 20, 3, 30)
// Future as a monad:
future.flatMap(value => anotherFuture(value))
Why this matters: for-comprehensions
Any type with flatMap, map, and withFilter works in a for-comprehension:
// Option monad
for {
a <- userById(1) // Option[User]
b <- emailFor(a) // Option[String]
c <- domainOf(b) // Option[String]
} yield c
// Returns Option[String]; None if any step failed
// Future monad
for {
user <- fetchUser(1)
orders <- fetchOrders(user.id)
} yield render(user, orders)
// Returns Future[String]
// List monad — Cartesian product
for {
x <- List(1, 2, 3)
y <- List('a', 'b')
} yield (x, y)
// List((1,a), (1,b), (2,a), (2,b), (3,a), (3,b))
Same syntax, different effects:
- Option: "missing value short-circuits"
- Future: "async chained operations"
- List: "for each combination"
- Either: "failure short-circuits"
- Try: "exception short-circuits"
Implementing your own monad-like type
For a Box[A]:
case class Box[A](value: A) {
def map[B](f: A => B): Box[B] = Box(f(value))
def flatMap[B](f: A => Box[B]): Box[B] = f(value)
}
val result = for {
a <- Box(2)
b <- Box(3)
} yield a + b
// Box(5)
Define map and flatMap correctly and you join the for-comprehension club.
The monad laws (briefly)
- Left identity:
unit(x).flatMap(f) === f(x) - Right identity:
m.flatMap(unit) === m - Associativity:
m.flatMap(f).flatMap(g) === m.flatMap(x => f(x).flatMap(g))
When these hold, your type is a real monad and composes predictably. If they don't, weird bugs in for-comprehensions.
When to define a monad?
- Your type has a sequencing semantic (do this, then that)
- You want for-comp ergonomics for users
- You're building an effect type (IO, State, Reader, Writer)
Don't reach for monads for simple data containers. Use case class. Monads are for things with sequenced effects.
Cats and ZIO
The Cats library generalizes this: Functor, Applicative, Monad as type classes. IO, Reader, State, Writer, EitherT as concrete monads. Cats Effect 3 / ZIO 2 are the modern functional effect systems for Scala — heavyweight ecosystems that build on the monad abstraction.
Common mistakes
- Confusing
mapwithflatMap— map forA => B; flatMap forA => M[B]. flatMap also "flattens" the result. - Implementing flatMap with side effects — breaks the laws. flatMap should be pure transformation.
forloops vs for-comp —for (x <- list) print(x)is iteration (no yield);for { ... } yield ...is monadic.- Nested Future in flatMap result — use flatMap, not map.
m.map(f)where f returns Future givesFuture[Future[X]]instead ofFuture[X]. - Reaching for Cats/ZIO without understanding the basics — learn the standard-library monads first.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…