DEV Community

Andrew (he/him)
Andrew (he/him)

Posted on

(Kinda sloppy) Monad-based Options in Scala

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)
}
Enter fullscreen mode Exit fullscreen mode

Further reading:

Discussion (0)