// Conrad Cunningham's adaptation of the Option functions and other code
// from the "Error Handling" notes (chapter 4).

import scala.{Option => _, Either => _, _} // hide standard

sealed trait Option[+A] {
  def map[B](f: A => B): Option[B] = this match {
    case None    => None
    case Some(a) => Some(f(a))
  }

  def getOrElse[B >: A](default: => B): B = this match {
    case None    => default // evaluate the thunk
    case Some(a) => a
  }

  def flatMap[B](f: A => Option[B]): Option[B] =
    map(f) getOrElse None

  def flatMap_1[B](f: A => Option[B]): Option[B] = this match {
    case None    => None
    case Some(a) => f(a)
  }

  def orElse[B >: A](ob: => Option[B]): Option[B] =
    this map (Some(_)) getOrElse ob

  def orElse_1[B>:A](ob: => Option[B]): Option[B] =
    this match {
      case None => ob
      case _    => this
    }

  def filter(p: A => Boolean): Option[A] = 
    this match {
      case Some(a) if p(a) => this
      case _ => None
    }

  def filter_1(p: A => Boolean): Option[A] =
    flatMap(a => if (p(a)) Some(a) else None)

}

case class Some[+A](get: A) extends Option[A]
case object None extends Option[Nothing]

import Option._

object Option {

  def mean(xs: Seq[Double]): Option[Double] =
    if (xs.isEmpty) 
      None
    else 
      Some(xs.sum / xs.length)

  def variance(xs: Seq[Double]): Option[Double] =
    mean(xs) flatMap (m => mean(xs.map(x => math.pow(x - m, 2))))


  def lift[A, B](f: A => B): Option[A] => Option[B] =
    _.map(f)

  def sqrtO: Option[Double] => Option[Double] =
    lift(math.sqrt _)

  def map2[A,B,C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] =
    a flatMap (aa => 
    b map (bb => 
    f(aa, bb)))
 			
  def map2fc[A,B,C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] =
    for {
      aa <- a
      bb <- b 
    } yield f(aa,bb)

}
