Worked on this one for a while today. What do you think? What would you change?
There's a bit of a tradeoff between making the type parameters a bit "cleaner" vs. the same for the method signatures in the Option
... I'm not sure which way to go with it.
object Monads extends App {
trait Functor[+A, F[_]] {
def map[B](f: A => B): F[B]
}
trait Monad[+A, F[_]] extends Functor[A, F] {
def unit[X](a: => X): F[X]
def flatMap[B](f: A => F[B]): F[B]
def map[B](f: A => B): F[B] = flatMap(a => unit(f(a)))
def flatten[B](implicit ev: A <:< F[B]): F[B] = flatMap[B](identity[A])
}
sealed trait Option[+T] extends Monad[T, Option] {
import Option._
override def unit[X](a: => X): Option[X] = Some(a)
override def flatMap[B](f: T => Option[B]): Option[B] = this match {
case Some(x) => f(x)
case None => None
}
}
object Option {
case class Some[T](x: T) extends Option[T]
case object None extends Option[Nothing]
}
import Option._
val somePinned: Option[Int] = Some(42)
val nonePinned: Option[Int] = None
println(somePinned.map(_ * 2)) // Some(42)
println(nonePinned.map(_ * 2)) // None
println(Some(Some(42))) // Some(Some(42))
println(Some(Some(42)).flatMap(x => x)) // Some(42)
println(Some(Some(42)).flatten) // Some(42)
}
Further reading:
- https://www.baeldung.com/scala/higher-kinded-types
- https://medium.com/free-code-camp/demystifying-the-monad-in-scala-cc716bb6f534
- https://blog.redelastic.com/a-guide-to-scala-collections-exploring-monads-in-scala-collections-ef810ef3aec3
- https://systemfw.org/posts/flatten.html
- https://livebook.manning.com/book/functional-programming-in-scala