Skip to content

Commit 4026f3f

Browse files
author
Javier de Silóniz Sandino
authored
Merge pull request #8 from scala-exercises/js-chapter4-exercises
Chapter 4 exercises - Error Handling with Option and Either
2 parents a2ed685 + aec718b commit 4026f3f

File tree

10 files changed

+773
-7
lines changed

10 files changed

+773
-7
lines changed

src/main/scala/fpinscalalib/ErrorHandlingSection.scala

Lines changed: 440 additions & 0 deletions
Large diffs are not rendered by default.

src/main/scala/fpinscalalib/FPinScalaLibrary.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ object FPinScalaLibrary extends Library {
1414

1515
override def sections = scala.collection.immutable.List(
1616
GettingStartedWithFPSection,
17-
FunctionalDataStructuresSection
17+
FunctionalDataStructuresSection,
18+
ErrorHandlingSection
1819
)
1920
}

src/main/scala/fpinscalalib/FunctionalDataStructuresSection.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package fpinscalalib
22

33
import org.scalatest.{FlatSpec, Matchers}
4-
import fpinscalalib.List._
5-
import fpinscalalib.Tree._
4+
import fpinscalalib.customlib._
5+
import fpinscalalib.customlib.functionaldatastructures._
6+
import fpinscalalib.customlib.functionaldatastructures.List._
7+
import Tree._
68

79
/** @param name functional_data_structures
810
*/
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package fpinscalalib.customlib.errorhandling
2+
3+
// The following implementation of the Either type is provided by Manning as a solution to the multiple implementation
4+
// exercises found in the "Functional Programming in Scala" book. We'll use this one (instead of Scala's Either), in our
5+
// sections related to chapter 4: "Handling error without exceptions". The original code can be found in the following
6+
// URL:
7+
//
8+
// https://github.com/fpinscala/fpinscala/blob/d9476b3323e843d234454ac411a61d353809deee/answers/src/main/scala/fpinscala/errorhandling/Either.scala
9+
10+
import scala.{Option => _, Left => _, Right => _, Either => _, _}
11+
12+
sealed trait Either[+E,+A] {
13+
def map[B](f: A => B): Either[E, B] =
14+
this match {
15+
case Right(a) => Right(f(a))
16+
case Left(e) => Left(e)
17+
}
18+
19+
def flatMap[EE >: E, B](f: A => Either[EE, B]): Either[EE, B] =
20+
this match {
21+
case Left(e) => Left(e)
22+
case Right(a) => f(a)
23+
}
24+
def orElse[EE >: E, AA >: A](b: => Either[EE, AA]): Either[EE, AA] =
25+
this match {
26+
case Left(_) => b
27+
case Right(a) => Right(a)
28+
}
29+
def map2[EE >: E, B, C](b: Either[EE, B])(f: (A, B) => C):
30+
Either[EE, C] = for { a <- this; b1 <- b } yield f(a,b1)
31+
}
32+
case class Left[+E](get: E) extends Either[E,Nothing]
33+
case class Right[+A](get: A) extends Either[Nothing,A]
34+
35+
object Either {
36+
def mean(xs: IndexedSeq[Double]): Either[String, Double] =
37+
if (xs.isEmpty)
38+
Left("mean of empty list!")
39+
else
40+
Right(xs.sum / xs.length)
41+
42+
def safeDiv(x: Int, y: Int): Either[Exception, Int] =
43+
try Right(x / y)
44+
catch { case e: Exception => Left(e) }
45+
46+
def Try[A](a: => A): Either[Exception, A] =
47+
try Right(a)
48+
catch { case e: Exception => Left(e) }
49+
50+
def traverse[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] =
51+
es match {
52+
case Nil => Right(Nil)
53+
case h::t => (f(h) map2 traverse(t)(f))(_ :: _)
54+
}
55+
56+
def traverse_1[E,A,B](es: List[A])(f: A => Either[E, B]): Either[E, List[B]] =
57+
es.foldRight[Either[E,List[B]]](Right(Nil))((a, b) => f(a).map2(b)(_ :: _))
58+
59+
def sequence[E,A](es: List[Either[E,A]]): Either[E,List[A]] =
60+
traverse(es)(x => x)
61+
62+
/*
63+
There are a number of variations on `Option` and `Either`. If we want to accumulate multiple errors, a simple
64+
approach is a new data type that lets us keep a list of errors in the data constructor that represents failures:
65+
66+
trait Partial[+A,+B]
67+
case class Errors[+A](get: Seq[A]) extends Partial[A,Nothing]
68+
case class Success[+B](get: B) extends Partial[Nothing,B]
69+
70+
There is a type very similar to this called `Validation` in the Scalaz library. You can implement `map`, `map2`,
71+
`sequence`, and so on for this type in such a way that errors are accumulated when possible (`flatMap` is unable to
72+
accumulate errors--can you see why?). This idea can even be generalized further--we don't need to accumulate failing
73+
values into a list; we can accumulate values using any user-supplied binary function.
74+
75+
It's also possible to use `Either[List[E],_]` directly to accumulate errors, using different implementations of
76+
helper functions like `map2` and `sequence`.
77+
*/
78+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package fpinscalalib.customlib.errorhandling
2+
3+
case class Employee(name: String, department: String, manager: Option[String])
4+
5+
object ExampleHelper {
6+
val joe = Employee("Joe", "Finances", Some("Julie"))
7+
val mary = Employee("Mary", "IT", None)
8+
val izumi = Employee("Izumi", "IT", Some("Jaime"))
9+
10+
def lookupByName(name: String): Option[Employee] = name match {
11+
case "Joe" => Some(joe)
12+
case "Mary" => Some(mary)
13+
case "Izumi" => Some(izumi)
14+
case _ => None
15+
}
16+
17+
def lookupByNameViaEither(name: String): Either[String, Employee] = name match {
18+
case "Joe" => Right(joe)
19+
case "Mary" => Right(mary)
20+
case "Izumi" => Right(izumi)
21+
case _ => Left("Employee not found")
22+
}
23+
24+
def Try[A](a: => A): Option[A] =
25+
try Some(a)
26+
catch { case e: Exception => None }
27+
28+
def TryEither[A](a: => A): Either[String, A] =
29+
try Right(a)
30+
catch { case e: Exception => Left(e.getMessage) }
31+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package fpinscalalib.customlib.errorhandling
2+
3+
// The following implementation of the Option type is provided by Manning as a solution to the multiple implementation
4+
// exercises found in the "Functional Programming in Scala" book. We'll use this one (instead of Scala's Option), in our
5+
// sections related to chapter 4: "Handling error without exceptions". The original code can be found in the following
6+
// URL:
7+
//
8+
// https://github.com/fpinscala/fpinscala/blob/d9476b3323e843d234454ac411a61d353809deee/answers/src/main/scala/fpinscala/errorhandling/Option.scala
9+
10+
11+
//hide std library `Option`, `Some` and `Either`, since we are writing our own in this chapter
12+
import scala.{Either => _, Option => _, Some => _}
13+
14+
sealed trait Option[+A] {
15+
def map[B](f: A => B): Option[B] = this match {
16+
case None => None
17+
case Some(a) => Some(f(a))
18+
}
19+
20+
def getOrElse[B>:A](default: => B): B = this match {
21+
case None => default
22+
case Some(a) => a
23+
}
24+
25+
def flatMap[B](f: A => Option[B]): Option[B] =
26+
map(f) getOrElse None
27+
28+
/*
29+
Of course, we can also implement `flatMap` with explicit pattern matching.
30+
*/
31+
def flatMap_1[B](f: A => Option[B]): Option[B] = this match {
32+
case None => None
33+
case Some(a) => f(a)
34+
}
35+
36+
def orElse[B>:A](ob: => Option[B]): Option[B] =
37+
this map (Some(_)) getOrElse ob
38+
39+
/*
40+
Again, we can implement this with explicit pattern matching.
41+
*/
42+
def orElse_1[B>:A](ob: => Option[B]): Option[B] = this match {
43+
case None => ob
44+
case _ => this
45+
}
46+
47+
def filter(f: A => Boolean): Option[A] = this match {
48+
case Some(a) if f(a) => this
49+
case _ => None
50+
}
51+
/*
52+
This can also be defined in terms of `flatMap`.
53+
*/
54+
def filter_1(f: A => Boolean): Option[A] =
55+
flatMap(a => if (f(a)) Some(a) else None)
56+
}
57+
case class Some[+A](get: A) extends Option[A]
58+
case object None extends Option[Nothing]
59+
60+
61+
object Option {
62+
63+
def failingFn(i: Int): Int = {
64+
// `val y: Int = ...` declares `y` as having type `Int`, and sets it equal to the right hand side of the `=`.
65+
val y: Int = throw new Exception("fail!")
66+
try {
67+
val x = 42 + 5
68+
x + y
69+
}
70+
// A `catch` block is just a pattern matching block like the ones we've seen. `case e: Exception` is a pattern
71+
// that matches any `Exception`, and it binds this value to the identifier `e`. The match returns the value 43.
72+
catch { case e: Exception => 43 }
73+
}
74+
75+
def failingFn2(i: Int): Int = {
76+
try {
77+
val x = 42 + 5
78+
// A thrown Exception can be given any type; here we're annotating it with the type `Int`
79+
x + ((throw new Exception("fail!")): Int)
80+
}
81+
catch { case e: Exception => 43 }
82+
}
83+
84+
def mean(xs: Seq[Double]): Option[Double] =
85+
if (xs.isEmpty) None
86+
else Some(xs.sum / xs.length)
87+
88+
def variance(xs: Seq[Double]): Option[Double] =
89+
mean(xs) flatMap (m => mean(xs.map(x => math.pow(x - m, 2))))
90+
91+
// a bit later in the chapter we'll learn nicer syntax for
92+
// writing functions like this
93+
def map2[A,B,C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] =
94+
a flatMap (aa => b map (bb => f(aa, bb)))
95+
96+
/*
97+
Here's an explicit recursive version:
98+
*/
99+
def sequence[A](a: List[Option[A]]): Option[List[A]] =
100+
a match {
101+
case Nil => Some(Nil)
102+
case h :: t => h flatMap (hh => sequence(t) map (hh :: _))
103+
}
104+
/*
105+
It can also be implemented using `foldRight` and `map2`. The type annotation on `foldRight` is needed here; otherwise
106+
Scala wrongly infers the result type of the fold as `Some[Nil.type]` and reports a type error (try it!). This is an
107+
unfortunate consequence of Scala using subtyping to encode algebraic data types.
108+
*/
109+
def sequence_1[A](a: List[Option[A]]): Option[List[A]] =
110+
a.foldRight[Option[List[A]]](Some(Nil))((x,y) => map2(x,y)(_ :: _))
111+
112+
def traverse[A, B](a: List[A])(f: A => Option[B]): Option[List[B]] =
113+
a match {
114+
case Nil => Some(Nil)
115+
case h::t => map2(f(h), traverse(t)(f))(_ :: _)
116+
}
117+
118+
def traverse_1[A, B](a: List[A])(f: A => Option[B]): Option[List[B]] =
119+
a.foldRight[Option[List[B]]](Some(Nil))((h,t) => map2(f(h),t)(_ :: _))
120+
121+
def sequenceViaTraverse[A](a: List[Option[A]]): Option[List[A]] =
122+
traverse(a)(x => x)
123+
}

src/main/scala/fpinscalalib/ListHelper.scala renamed to src/main/scala/fpinscalalib/customlib/functionaldatastructures/ListHelper.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package fpinscalalib
1+
package fpinscalalib.customlib.functionaldatastructures
22

33
// The following implementation of the singly linked list is provided by Manning as a solution to the multiple implementation
44
// exercises found in the "Functional Programming in Scala" book. We'll use this one (instead of Scala's List), in our

src/main/scala/fpinscalalib/TreeHelper.scala renamed to src/main/scala/fpinscalalib/customlib/functionaldatastructures/TreeHelper.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
package fpinscalalib
1+
package fpinscalalib.customlib.functionaldatastructures
22

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

88
sealed trait Tree[+A]
99
case class Leaf[A](value: A) extends Tree[A]
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package fpinscalalib
2+
3+
import org.scalacheck.Shapeless._
4+
import org.scalacheck.{Arbitrary, Gen}
5+
import org.scalaexercises.Test
6+
import org.scalatest.Spec
7+
import org.scalatest.prop.Checkers
8+
import shapeless.HNil
9+
import fpinscalalib.customlib.errorhandling._
10+
import fpinscalalib.customlib.errorhandling.Employee
11+
import fpinscalalib.customlib.errorhandling.ExampleHelper._
12+
13+
class ErrorHandlingSpec extends Spec with Checkers {
14+
def `option mean asserts` = {
15+
val none : Option[Double] = None
16+
17+
check(Test.testSuccess(ErrorHandlingSection.optionMeanAssert _, none :: HNil))
18+
}
19+
20+
def `option map asserts` = {
21+
val f = (e: Option[Employee]) => e.map(_.department)
22+
23+
check(Test.testSuccess(ErrorHandlingSection.optionMapAssert _, f :: HNil))
24+
}
25+
26+
def `option flatMap asserts` = {
27+
val f = (e: Option[Employee]) => e.flatMap(_.manager)
28+
29+
check(Test.testSuccess(ErrorHandlingSection.optionFlatMapAssert _, f :: HNil))
30+
}
31+
32+
def `option orElse asserts` = {
33+
check(Test.testSuccess(ErrorHandlingSection.optionOrElseAssert _,
34+
Some("Julie") :: Some("Mr. CEO") :: Some("Mr. CEO") :: HNil))
35+
}
36+
37+
def `option filter asserts` = {
38+
val none : Option[Employee] = None
39+
check(Test.testSuccess(ErrorHandlingSection.optionFilterAssert _,
40+
Some(joe) :: none :: none :: HNil))
41+
}
42+
43+
def `option sequence asserts` = {
44+
val none : Option[List[Int]] = None
45+
check(Test.testSuccess(ErrorHandlingSection.optionSequenceAssert _, Some(List(1, 2, 3)) :: none :: HNil))
46+
}
47+
48+
def `option traverse asserts` = {
49+
val none : Option[List[Int]] = None
50+
check(Test.testSuccess(ErrorHandlingSection.optionTraverseAssert _, Some(List(1, 2, 3)) :: none :: HNil))
51+
}
52+
53+
def `either mean asserts` = {
54+
check(Test.testSuccess(ErrorHandlingSection.eitherMeanAssert _, Right(3.0) :: Left("mean of empty list!") :: HNil))
55+
}
56+
57+
def `either map asserts` = {
58+
val f = (e: Either[String, Employee]) => e.map(_.department)
59+
60+
check(Test.testSuccess(ErrorHandlingSection.eitherMapAssert _, f :: HNil))
61+
}
62+
63+
def `either flatMap asserts` = {
64+
check(Test.testSuccess(ErrorHandlingSection.eitherFlatMapAssert _,
65+
Right("Julie") :: Left("Manager not found") :: Left("Employee not found") :: HNil))
66+
}
67+
68+
def `either orElse asserts` = {
69+
check(Test.testSuccess(ErrorHandlingSection.eitherOrElseAssert _,
70+
Right("Julie") :: Right("Mr. CEO") :: Right("Mr. CEO") :: HNil))
71+
}
72+
73+
def `either map2 asserts` = {
74+
check(Test.testSuccess(ErrorHandlingSection.eitherMap2Assert _,
75+
Right(false) :: Right(true) :: Left("Employee not found") :: HNil))
76+
}
77+
78+
def `either traverse asserts` = {
79+
val list = List(joe, mary)
80+
check(Test.testSuccess(ErrorHandlingSection.eitherTraverseAssert _,
81+
Right(list) :: Left("Employee not found") :: HNil))
82+
}
83+
84+
def `either sequence asserts` = {
85+
val list = List(joe, mary)
86+
check(Test.testSuccess(ErrorHandlingSection.eitherSequenceAssert _,
87+
Right(list) :: Left("Employee not found") :: HNil))
88+
}
89+
}

src/test/scala/fpinscalalib/FunctionalDataStructuresSpec.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
package fpinscalalib
22

3+
import fpinscalalib.customlib.functionaldatastructures.{Branch, Leaf}
34
import org.scalacheck.Shapeless._
45
import org.scalacheck.{Arbitrary, Gen}
56
import org.scalaexercises.Test
67
import org.scalatest.Spec
78
import org.scalatest.prop.Checkers
89
import shapeless.HNil
10+
import fpinscalalib.customlib.functionaldatastructures._
911

1012
class FunctionalDataStructuresSpec extends Spec with Checkers {
1113
def `pattern matching 101 asserts` = {
1214
check(Test.testSuccess(FunctionalDataStructuresSection.patternMatching101Assert _,
13-
42 :: 1 :: fpinscalalib.List(2, 3) :: HNil))
15+
42 :: 1 :: List(2, 3) :: HNil))
1416
}
1517

1618
def `complex pattern matching asserts` = {

0 commit comments

Comments
 (0)