-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Abhijit Sarkar
committed
Dec 17, 2023
1 parent
80eab21
commit 443fa7d
Showing
5 changed files
with
210 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
enum Either[+E, +A]: | ||
case Left(value: E) | ||
case Right(value: A) | ||
|
||
/* | ||
Exercise 4.6: Implement versions of map, flatMap, orElse, and map2 | ||
on Either that operate on the Right value. | ||
*/ | ||
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 Right(a) => f(a) | ||
case Left(e) => Left(e) | ||
|
||
def orElse[EE >: E, B >: A](b: => Either[EE, B]): Either[EE, B] = this match | ||
case Right(a) => Right(a) | ||
case Left(_) => b | ||
|
||
def map2[EE >: E, B, C](that: Either[EE, B])(f: (A, B) => C): Either[EE, C] = | ||
// flatMap(x => that.map(y => f(x, y))) | ||
for | ||
a <- this | ||
b <- that | ||
yield f(a, b) | ||
|
||
object Either: | ||
/* | ||
Exercise 4.7: Implement sequence and traverse for Either. These should return | ||
the first error that's encountered if there is one. | ||
*/ | ||
def sequence[E, A](as: List[Either[E, A]]): Either[E, List[A]] = | ||
traverse(as)(a => a) | ||
|
||
def traverse[E, A, B](as: List[A])(f: A => Either[E, B]): Either[E, List[B]] = | ||
as match | ||
case x :: xs => f(x).flatMap(a => traverse(xs)(f).map(a :: _)) | ||
case _ => Right(Nil) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
enum Option[+A]: | ||
case Some(get: A) | ||
case None | ||
|
||
/* | ||
Exersie 4.1: Implement the following functions on Option. | ||
You should be able to implement all the functions besides | ||
map and getOrElse without resorting to pattern matching. | ||
Try implementing flatMap, orElse, and filter in terms of | ||
map and getOrElse. | ||
*/ | ||
def map[B](f: A => B): Option[B] = this match | ||
case None => None | ||
case Some(x) => Some(f(x)) | ||
|
||
def getOrElse[B >: A](default: => B): B = this match | ||
case None => default | ||
case Some(x) => x | ||
|
||
def flatMap[B](f: A => Option[B]): Option[B] = | ||
map(f).getOrElse(None) | ||
|
||
def orElse[B >: A](ob: => Option[B]): Option[B] = | ||
map(Some(_)).getOrElse(ob) | ||
|
||
def filter(f: A => Boolean): Option[A] = | ||
// map(x => if f(x) then Some(x) else None) | ||
// .getOrElse(None) | ||
flatMap(x => if f(x) then this else None) | ||
|
||
object Option: | ||
def mean(xs: Seq[Double]): Option[Double] = | ||
if xs.isEmpty then None | ||
else Some(xs.sum / xs.length) | ||
|
||
/* | ||
Exercise 4.2: Implement the variance function in terms of flatMap. | ||
If the mean of a sequence is m, the variance is the mean of math.pow(x - m, 2) | ||
for each element of the sequence. | ||
*/ | ||
def variance(xs: Seq[Double]): Option[Double] = | ||
mean(xs).flatMap(m => mean(xs.map(x => math.pow(x - m, 2)))) | ||
|
||
/* | ||
Exercise 4.3: Write a generic function map2 that combines two Option values | ||
using a binary function. If either Option is None, then the return value is too. | ||
*/ | ||
def map2[A, B, C](a: Option[A], b: Option[B])(f: (A, B) => C): Option[C] = | ||
a.flatMap(x => b.map(y => f(x, y))) | ||
|
||
/* | ||
Exercise 4.4: Write a function sequence that combines a list of Options into | ||
one Option containing a list of all the Some values on the original list. | ||
If the original list contains None even once, the result of the function | ||
should be None; otherwise, the result should be Some, with a list of all | ||
the values. | ||
*/ | ||
def sequence[A](as: List[Option[A]]): Option[List[A]] = | ||
traverse(as)(a => a) | ||
|
||
/* | ||
Exercise 4.5: Sometimes we'll want to map over a list using a function | ||
that might fail, returning None if applying it to any element of the | ||
list returns None. | ||
Implement this function. It's straightforward to do using map and sequence, | ||
but try for a more efficient implementation that only looks at the list once. | ||
In fact, implement sequence in in terms of traverse. | ||
*/ | ||
def traverse[A, B](as: List[A])(f: A => Option[B]): Option[List[B]] = | ||
as match | ||
case x :: xs => f(x).flatMap(a => traverse(xs)(f).map(a :: _)) | ||
case _ => Some(Nil) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import Either.* | ||
import org.scalatest.funspec.AnyFunSpec | ||
import org.scalatest.matchers.should.Matchers.shouldBe | ||
|
||
class EitherSpec extends AnyFunSpec: | ||
describe("Either"): | ||
it("map should transform a Right"): | ||
Right(2).map(_ + 2) shouldBe Right(4) | ||
|
||
it("map should have no effect on a Left"): | ||
(Left(2): Either[Int, Int]).map(_ + 2) shouldBe Left(2) | ||
|
||
it("flatMap should transform a Right"): | ||
Right(2).flatMap(x => Right(x + 2)) shouldBe Right(4) | ||
|
||
it("flatMap should have no effect on a None"): | ||
(Left(2): Either[Int, Int]).flatMap(x => Right(x + 2)) shouldBe Left(2) | ||
|
||
it("orElse should return the first Either if it's a Right"): | ||
Right(2).orElse(???) shouldBe Right(2) | ||
|
||
it("orElse should return the second Either when invoked on a Left"): | ||
(Left(2): Either[Int, Int]).orElse { Right(3) } shouldBe Right(3) | ||
(Left(2): Either[Int, Int]).orElse { Left(3) } shouldBe Left(3) | ||
|
||
it("map2 should combine two Either values"): | ||
Right(2).map2(Right(3))(_ * _) shouldBe Right(6) | ||
|
||
it("map2 should return Left if any one of the arguments is a Left"): | ||
val l: Either[Int, Int] = Left(3) | ||
val r: Either[Int, Int] = Right(2) | ||
|
||
r.map2(l)(_ * _) shouldBe Left(3) | ||
l.map2(r)(_ * _) shouldBe Left(3) | ||
l.map2(l)(_ * _) shouldBe Left(3) | ||
|
||
it("sequence should collect all the Right values"): | ||
sequence(List(Right(1), Right(2), Right(3))) shouldBe Right(List(1, 2, 3)) | ||
|
||
it("sequence should stop at first Left"): | ||
sequence(List(Right(1), Left(2), Right(3))) shouldBe Left(2) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import Option.* | ||
import org.scalatest.funspec.AnyFunSpec | ||
import org.scalatest.matchers.should.Matchers.shouldBe | ||
|
||
class OptionSpec extends AnyFunSpec: | ||
describe("Option"): | ||
it("map should transform a Some"): | ||
Some(2).map(_ + 2) shouldBe Some(4) | ||
|
||
it("map should have no effect on a None"): | ||
Some(2).filter(_ % 2 == 1).map(_ + 2) shouldBe None | ||
|
||
it("getOrElse should return the value inside a Some"): | ||
Some(2).getOrElse(???) shouldBe 2 | ||
|
||
it("getOrElse should return the default value when invoked on a None"): | ||
Some(2).filter(_ % 2 == 1).getOrElse { 3 } shouldBe 3 | ||
|
||
it("filter should keep the value if it satisfies the given predicate"): | ||
Some(2).filter(_ % 2 == 0) shouldBe Some(2) | ||
|
||
it("filter should convert Some to None if the value doesn't satisfy the given predicate"): | ||
Some(2).filter(_ % 2 == 1) shouldBe None | ||
|
||
it("flatMap should transform a Some"): | ||
Some(2).flatMap(x => Some(x + 2)) shouldBe Some(4) | ||
|
||
it("flatMap should have no effect on a None"): | ||
Some(2).filter(_ % 2 == 1).flatMap(x => Some(x + 2)) shouldBe None | ||
|
||
it("orElse should return the first Option if it's a Some"): | ||
Some(2).orElse(???) shouldBe Some(2) | ||
|
||
it("orElse should return the second Option when invoked on a None"): | ||
Some(2).filter(_ % 2 == 1).orElse { Some(3) } shouldBe Some(3) | ||
|
||
it("map2 should combine two Some values"): | ||
map2(Some(2), Some(3))(_ * _) shouldBe Some(6) | ||
|
||
it("map2 should return None if any one of the arguments is a None"): | ||
map2(Some(2), None: Option[Int])(_ * _) shouldBe None | ||
map2(None: Option[Int], Some(2))(_ * _) shouldBe None | ||
map2(None: Option[Int], None: Option[Int])(_ * _) shouldBe None | ||
|
||
it("sequence should collect all the Some values"): | ||
sequence(List(Some(1), Some(2), Some(3))) shouldBe Some(List(1, 2, 3)) | ||
|
||
it("sequence should stop at first None"): | ||
sequence(List(Some(1), None, Some(2))) shouldBe None |