## Functional programming in Scala, Chapter 4, Option type


object Example {

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

def flatMap[B](f: A => Option[B]): Option[B] = this match {
case None => None
case Some(x) => f(x) match {
case None => None
case y => y
}
}

def getOrElse[B >: A](default: => B): B = {
this match {
case None => default
case Some(x) => x
}
}

def orElse[B >: A](ob: => Option[B]): Option[B] = {
if (this == None) ob
else this
}

def filter(f: A => Boolean): Option[A] = this match {
case None => None
case y@Some(x) => if (f(x)) y else None
}
}

case class Some[+A](val get: A) extends Option[A]

case object None extends Option[Nothing]

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 map2[A, B, C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] = {
(a, b) match {
case (None, _) => None
case (_, None) => None
case (Some(x), Some(y)) => Some(f(x, y))
}
}
def map2a[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 map2b[A, B, C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] = {
for {
aa <- a
bb <- b
} yield f(aa, bb)
}

def sequence[A](a: List[Option[A]]): Option[List[A]] =
a.foldRight[Option[List[A]]](Some(Nil))((x,y) => map2(x,y)(_ :: _))

def parseInts(as: List[String]): Option[List[Int]] = {
traverse(as)(a => Try(a.toInt))
}

def traverse1[A, B](as: List[A])(f: A => Option[B]): Option[List[B]] = {
as match {
case Nil => Some(Nil)
case x :: xs => map2(f(x), traverse(as)(f))(_ :: _)
}
}
def traverse[A, B](as: List[A])(f: A => Option[B]): Option[List[B]] = {
as.foldRight[Option[List[B]]](Some(Nil))((h, t) => map2(f(h), t)(_ :: _))
}

def sequence2[A](a: List[Option[A]]): Option[List[A]] =
traverse(a)(x => x)

def positiveOption(x: Int): Option[Int] = {
if(x > 0) Some(x) else None
}

def Try[A](a: => A): Option[A] =
try Some(a)
catch {
case e: Exception => {
println(e)
None
}
}

def parseInsuranceRateQuote(age: String, numberOfSpeedingTickets: String): Option[Double] = {
val optAge: Option[Int] = Try(age.toInt)
val optTickets: Option[Int] = Try(numberOfSpeedingTickets.toInt)
map2(optAge, optTickets)(insuranceRateQuote)
}

def insuranceRateQuote(age: Int, numberOfSpeedingTickets: Int): Double = {
age + (numberOfSpeedingTickets * 2)
}
}

object Test {

import Example._

//  Some(1).map(_ + 1)
//  Some(-1).filter(_ > 0)
//  None.getOrElse(3)
//  Some(4).getOrElse(5)
//  None.orElse(Some(12))
//  Some(Seq.empty[Double]).flatMap(mean(_))
//  Some(Seq.empty[Double]).map(mean(_))

lift(math.abs)(None)
lift(math.abs)(Some(-3))

traverse(List(1, 2, -3, -4))(positiveOption(_))
traverse(List(-4, 1, 2, -3, -4))(positiveOption(_))
traverse(List(1, 2, 3, 4))(positiveOption)
sequence2(List(Some(1), Some(2)))
sequence2(List(Some(1), Some(2), None))
parseInts(List("5", "4", "3"))
parseInts(List("5", "4", "3", "world"))
}