Skip to content

Chapter 4 exercises - Error Handling with Option and Either #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 8, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
440 changes: 440 additions & 0 deletions src/main/scala/fpinscalalib/ErrorHandlingSection.scala

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/main/scala/fpinscalalib/FPinScalaLibrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ object FPinScalaLibrary extends Library {

override def sections = scala.collection.immutable.List(
GettingStartedWithFPSection,
FunctionalDataStructuresSection
FunctionalDataStructuresSection,
ErrorHandlingSection
)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package fpinscalalib

import org.scalatest.{FlatSpec, Matchers}
import fpinscalalib.List._
import fpinscalalib.Tree._
import fpinscalalib.customlib._
import fpinscalalib.customlib.functionaldatastructures._
import fpinscalalib.customlib.functionaldatastructures.List._
import Tree._

/** @param name functional_data_structures
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package fpinscalalib.customlib.errorhandling

// The following implementation of the Either type is provided by Manning as a solution to the multiple implementation
// exercises found in the "Functional Programming in Scala" book. We'll use this one (instead of Scala's Either), in our
// sections related to chapter 4: "Handling error without exceptions". The original code can be found in the following
// URL:
//
// https://github.com/fpinscala/fpinscala/blob/d9476b3323e843d234454ac411a61d353809deee/answers/src/main/scala/fpinscala/errorhandling/Either.scala

import scala.{Option => _, Left => _, Right => _, Either => _, _}

sealed trait Either[+E,+A] {
def map[B](f: A => B): Either[E, B] =
this match {
case Right(a) => Right(f(a))
case Left(e) => Left(e)
}

def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] =
this match {
case Left(e) => Left(e)
case Right(a) => f(a)
}
def orElse[EE >: E, AA >: A](b: => Either[EE, AA]): Either[EE, AA] =
this match {
case Left(_) => b
case Right(a) => Right(a)
}
def map2[EE >: E, B, C](b: Either[EE, B])(f: (A, B) => C):
Either[EE, C] = for { a <- this; b1 <- b } yield f(a,b1)
}
case class Left[+E](get: E) extends Either[E,Nothing]
case class Right[+A](get: A) extends Either[Nothing,A]

object Either {
def mean(xs: IndexedSeq[Double]): Either[String, Double] =
if (xs.isEmpty)
Left("mean of empty list!")
else
Right(xs.sum / xs.length)

def safeDiv(x: Int, y: Int): Either[Exception, Int] =
try Right(x / y)
catch { case e: Exception => Left(e) }

def Try[A](a: => A): Either[Exception, A] =
try Right(a)
catch { case e: Exception => Left(e) }

def traverse[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] =
es match {
case Nil => Right(Nil)
case h::t => (f(h) map2 traverse(t)(f))(_ :: _)
}

def traverse_1[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] =
es.foldRight[Either[E,List[B]]](Right(Nil))((a, b) => f(a).map2(b)(_ :: _))

def sequence[E,A](es: List[Either[E,A]]): Either[E,List[A]] =
traverse(es)(x => x)

/*
There are a number of variations on `Option` and `Either`. If we want to accumulate multiple errors, a simple
approach is a new data type that lets us keep a list of errors in the data constructor that represents failures:

trait Partial[+A,+B]
case class Errors[+A](get: Seq[A]) extends Partial[A,Nothing]
case class Success[+B](get: B) extends Partial[Nothing,B]

There is a type very similar to this called `Validation` in the Scalaz library. You can implement `map`, `map2`,
`sequence`, and so on for this type in such a way that errors are accumulated when possible (`flatMap` is unable to
accumulate errors--can you see why?). This idea can even be generalized further--we don't need to accumulate failing
values into a list; we can accumulate values using any user-supplied binary function.

It's also possible to use `Either[List[E],_]` directly to accumulate errors, using different implementations of
helper functions like `map2` and `sequence`.
*/
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package fpinscalalib.customlib.errorhandling

case class Employee(name: String, department: String, manager: Option[String])

object ExampleHelper {
val joe = Employee("Joe", "Finances", Some("Julie"))
val mary = Employee("Mary", "IT", None)
val izumi = Employee("Izumi", "IT", Some("Jaime"))

def lookupByName(name: String): Option[Employee] = name match {
case "Joe" => Some(joe)
case "Mary" => Some(mary)
case "Izumi" => Some(izumi)
case _ => None
}

def lookupByNameViaEither(name: String): Either[String, Employee] = name match {
case "Joe" => Right(joe)
case "Mary" => Right(mary)
case "Izumi" => Right(izumi)
case _ => Left("Employee not found")
}

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

def TryEither[A](a: => A): Either[String, A] =
try Right(a)
catch { case e: Exception => Left(e.getMessage) }
}
123 changes: 123 additions & 0 deletions src/main/scala/fpinscalalib/customlib/errorhandling/OptionHelper.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package fpinscalalib.customlib.errorhandling

// The following implementation of the Option type is provided by Manning as a solution to the multiple implementation
// exercises found in the "Functional Programming in Scala" book. We'll use this one (instead of Scala's Option), in our
// sections related to chapter 4: "Handling error without exceptions". The original code can be found in the following
// URL:
//
// https://github.com/fpinscala/fpinscala/blob/d9476b3323e843d234454ac411a61d353809deee/answers/src/main/scala/fpinscala/errorhandling/Option.scala


//hide std library `Option`, `Some` and `Either`, since we are writing our own in this chapter
import scala.{Either => _, Option => _, Some => _}

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
case Some(a) => a
}

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

/*
Of course, we can also implement `flatMap` with explicit pattern matching.
*/
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

/*
Again, we can implement this with explicit pattern matching.
*/
def orElse_1[B>:A](ob: => Option[B]): Option[B] = this match {
case None => ob
case _ => this
}

def filter(f: A => Boolean): Option[A] = this match {
case Some(a) if f(a) => this
case _ => None
}
/*
This can also be defined in terms of `flatMap`.
*/
def filter_1(f: A => Boolean): Option[A] =
flatMap(a => if (f(a)) Some(a) else None)
}
case class Some[+A](get: A) extends Option[A]
case object None extends Option[Nothing]


object Option {

def failingFn(i: Int): Int = {
// `val y: Int = ...` declares `y` as having type `Int`, and sets it equal to the right hand side of the `=`.
val y: Int = throw new Exception("fail!")
try {
val x = 42 + 5
x + y
}
// A `catch` block is just a pattern matching block like the ones we've seen. `case e: Exception` is a pattern
// that matches any `Exception`, and it binds this value to the identifier `e`. The match returns the value 43.
catch { case e: Exception => 43 }
}

def failingFn2(i: Int): Int = {
try {
val x = 42 + 5
// A thrown Exception can be given any type; here we're annotating it with the type `Int`
x + ((throw new Exception("fail!")): Int)
}
catch { case e: Exception => 43 }
}

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))))

// a bit later in the chapter we'll learn nicer syntax for
// writing functions like this
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)))

/*
Here's an explicit recursive version:
*/
def sequence[A](a: List[Option[A]]): Option[List[A]] =
a match {
case Nil => Some(Nil)
case h :: t => h flatMap (hh => sequence(t) map (hh :: _))
}
/*
It can also be implemented using `foldRight` and `map2`. The type annotation on `foldRight` is needed here; otherwise
Scala wrongly infers the result type of the fold as `Some[Nil.type]` and reports a type error (try it!). This is an
unfortunate consequence of Scala using subtyping to encode algebraic data types.
*/
def sequence_1[A](a: List[Option[A]]): Option[List[A]] =
a.foldRight[Option[List[A]]](Some(Nil))((x,y) => map2(x,y)(_ :: _))

def traverse[A, B](a: List[A])(f: A => Option[B]): Option[List[B]] =
a match {
case Nil => Some(Nil)
case h::t => map2(f(h), traverse(t)(f))(_ :: _)
}

def traverse_1[A, B](a: List[A])(f: A => Option[B]): Option[List[B]] =
a.foldRight[Option[List[B]]](Some(Nil))((h,t) => map2(f(h),t)(_ :: _))

def sequenceViaTraverse[A](a: List[Option[A]]): Option[List[A]] =
traverse(a)(x => x)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package fpinscalalib
package fpinscalalib.customlib.functionaldatastructures

// The following implementation of the singly linked list is provided by Manning as a solution to the multiple implementation
// exercises found in the "Functional Programming in Scala" book. We'll use this one (instead of Scala's List), in our
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package fpinscalalib
package fpinscalalib.customlib.functionaldatastructures

// The following implementation of the binary tree is provided by Manning as a solution to the multiple implementation
// exercises found in the "Functional Programming in Scala" book. The original code can be found in the following URL:
//
// https://github.com/fpinscala/fpinscala/commit/5bf1138f3aa71ff91babaa99613313fb9ac48b27
// https://github.com/fpinscala/fpinscala/blob/5bf1138f3aa71ff91babaa99613313fb9ac48b27/answers/src/main/scala/fpinscala/datastructures/Tree.scala

sealed trait Tree[+A]
case class Leaf[A](value: A) extends Tree[A]
Expand Down
89 changes: 89 additions & 0 deletions src/test/scala/fpinscalalib/ErrorHandlingSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package fpinscalalib

import org.scalacheck.Shapeless._
import org.scalacheck.{Arbitrary, Gen}
import org.scalaexercises.Test
import org.scalatest.Spec
import org.scalatest.prop.Checkers
import shapeless.HNil
import fpinscalalib.customlib.errorhandling._
import fpinscalalib.customlib.errorhandling.Employee
import fpinscalalib.customlib.errorhandling.ExampleHelper._

class ErrorHandlingSpec extends Spec with Checkers {
def `option mean asserts` = {
val none : Option[Double] = None

check(Test.testSuccess(ErrorHandlingSection.optionMeanAssert _, none :: HNil))
}

def `option map asserts` = {
val f = (e: Option[Employee]) => e.map(_.department)

check(Test.testSuccess(ErrorHandlingSection.optionMapAssert _, f :: HNil))
}

def `option flatMap asserts` = {
val f = (e: Option[Employee]) => e.flatMap(_.manager)

check(Test.testSuccess(ErrorHandlingSection.optionFlatMapAssert _, f :: HNil))
}

def `option orElse asserts` = {
check(Test.testSuccess(ErrorHandlingSection.optionOrElseAssert _,
Some("Julie") :: Some("Mr. CEO") :: Some("Mr. CEO") :: HNil))
}

def `option filter asserts` = {
val none : Option[Employee] = None
check(Test.testSuccess(ErrorHandlingSection.optionFilterAssert _,
Some(joe) :: none :: none :: HNil))
}

def `option sequence asserts` = {
val none : Option[List[Int]] = None
check(Test.testSuccess(ErrorHandlingSection.optionSequenceAssert _, Some(List(1, 2, 3)) :: none :: HNil))
}

def `option traverse asserts` = {
val none : Option[List[Int]] = None
check(Test.testSuccess(ErrorHandlingSection.optionTraverseAssert _, Some(List(1, 2, 3)) :: none :: HNil))
}

def `either mean asserts` = {
check(Test.testSuccess(ErrorHandlingSection.eitherMeanAssert _, Right(3.0) :: Left("mean of empty list!") :: HNil))
}

def `either map asserts` = {
val f = (e: Either[String, Employee]) => e.map(_.department)

check(Test.testSuccess(ErrorHandlingSection.eitherMapAssert _, f :: HNil))
}

def `either flatMap asserts` = {
check(Test.testSuccess(ErrorHandlingSection.eitherFlatMapAssert _,
Right("Julie") :: Left("Manager not found") :: Left("Employee not found") :: HNil))
}

def `either orElse asserts` = {
check(Test.testSuccess(ErrorHandlingSection.eitherOrElseAssert _,
Right("Julie") :: Right("Mr. CEO") :: Right("Mr. CEO") :: HNil))
}

def `either map2 asserts` = {
check(Test.testSuccess(ErrorHandlingSection.eitherMap2Assert _,
Right(false) :: Right(true) :: Left("Employee not found") :: HNil))
}

def `either traverse asserts` = {
val list = List(joe, mary)
check(Test.testSuccess(ErrorHandlingSection.eitherTraverseAssert _,
Right(list) :: Left("Employee not found") :: HNil))
}

def `either sequence asserts` = {
val list = List(joe, mary)
check(Test.testSuccess(ErrorHandlingSection.eitherSequenceAssert _,
Right(list) :: Left("Employee not found") :: HNil))
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package fpinscalalib

import fpinscalalib.customlib.functionaldatastructures.{Branch, Leaf}
import org.scalacheck.Shapeless._
import org.scalacheck.{Arbitrary, Gen}
import org.scalaexercises.Test
import org.scalatest.Spec
import org.scalatest.prop.Checkers
import shapeless.HNil
import fpinscalalib.customlib.functionaldatastructures._

class FunctionalDataStructuresSpec extends Spec with Checkers {
def `pattern matching 101 asserts` = {
check(Test.testSuccess(FunctionalDataStructuresSection.patternMatching101Assert _,
42 :: 1 :: fpinscalalib.List(2, 3) :: HNil))
42 :: 1 :: List(2, 3) :: HNil))
}

def `complex pattern matching asserts` = {
Expand Down