Skip to content

Commit d684f6c

Browse files
author
Abhijit Sarkar
committed
Complete till ch12
1 parent eb1bc3e commit d684f6c

26 files changed

+607
-104
lines changed

.github/run.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ fi
4545

4646
if (( no_lint == 0 )); then
4747
if [[ -z "${CI}" ]]; then
48-
./mill mill.scalalib.scalafmt.ScalafmtModule/reformatAll modules[_].sources
48+
./mill mill.scalalib.scalafmt.ScalafmtModule/reformatAll modules[_].__.sources
4949
else
50-
./mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll modules[_].sources
50+
./mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll modules[_].__.sources
5151
fi
5252
fi

.mill-version

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.12.3
1+
0.12.4

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ The older code is available in branches.
1717
7. [Monoids and Semigroups](ch07)
1818
8. [Functors](ch08)
1919
9. [Monads](ch09)
20+
10. [Monad Transformers](ch10)
21+
11. [Semigroupal and Applicative](ch11)
22+
12. [Foldable and Traverse](ch12)
2023

2124
## Running tests
2225
```

build.mill

+1-3
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,9 @@ trait CatsModule extends ScalaModule with Cross.Module[String] with ScalafmtModu
3838
)
3939

4040
object test extends ScalaTests with TestModule.ScalaTest {
41-
val commonDeps = Seq(
41+
def ivyDeps = Agg(
4242
ivy"org.scalatest::scalatest:${v.scalatestVersion}",
4343
ivy"org.scalatestplus::scalacheck-1-18:${v.scalacheckVersion}"
4444
)
45-
46-
def ivyDeps = Task{commonDeps}
4745
}
4846
}

ch02/test/src/MyListSpec.scala

-7
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,3 @@ class MyListSpec extends AnyFunSpec:
3030
it("evens"):
3131
val actual = MyList.iterate(0, 5)(_ + 1).map(_ * 2)
3232
actual.toSeq shouldBe (0 to 8 by 2)
33-
34-
35-
36-
37-
38-
39-

ch03/test/src/BoolSpec.scala

-1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,3 @@ class BoolSpec extends AnyFunSpec:
2020
it("not"):
2121
not(True).`if`("yes")("no") shouldBe "no"
2222
not(False).`if`("yes")("no") shouldBe "yes"
23-

ch03/test/src/CodataSpec.scala

-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,3 @@ class CodataSpec extends AnyFunSpec:
1111

1212
val product = list()(1, (a, b) => a * b)
1313
product shouldBe 6
14-

ch03/test/src/SetSpec.scala

+5-6
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ class SetSpec extends AnyFunSpec:
1111
Evens.union(ListSet.empty.insert(1).insert(3))
1212

1313
evensAndOne.contains(1) shouldBe true
14-
evensAndOthers.contains(1) shouldBe true
15-
evensAndOne.contains(2) shouldBe true
16-
evensAndOthers.contains(2) shouldBe true
17-
evensAndOne.contains(3) shouldBe false
18-
evensAndOthers.contains(3) shouldBe true
19-
14+
evensAndOthers.contains(1) shouldBe true
15+
evensAndOne.contains(2) shouldBe true
16+
evensAndOthers.contains(2) shouldBe true
17+
evensAndOne.contains(3) shouldBe false
18+
evensAndOthers.contains(3) shouldBe true

ch03/test/src/StreamSpec.scala

-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,3 @@ class StreamSpec extends AnyFunSpec:
1818
Stream.naturals.take(5) shouldBe (1 to 5)
1919
Stream.naturals2.take(5) shouldBe (1 to 5)
2020
Stream.naturals3.take(5) shouldBe (1 to 5)
21-
22-
23-

ch05/test/src/ExpressionCSpec.scala

-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,3 @@ class ExpressionCSpec extends AnyFunSpec:
88
it("eval"):
99
val fortyTwo = ((ExpressionC(15.0) + ExpressionC(5.0)) * ExpressionC(2.0) + ExpressionC(2.0)) / ExpressionC(1.0)
1010
fortyTwo.eval shouldBe 42.0d
11-

ch05/test/src/ExpressionSpec.scala

-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,3 @@ class ExpressionSpec extends AnyFunSpec:
88
it("eval"):
99
val fortyTwo = ((Expression(15.0) + Expression(5.0)) * Expression(2.0) + Expression(2.0)) / Expression(1.0)
1010
fortyTwo.eval shouldBe 42.0d
11-

ch05/test/src/ExpressionTSpec.scala

-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,3 @@ class ExpressionTSpec extends AnyFunSpec:
88
it("eval"):
99
val fortyTwo = ((ExpressionT(15.0) + ExpressionT(5.0)) * ExpressionT(2.0) + ExpressionT(2.0)) / ExpressionT(1.0)
1010
fortyTwo.eval shouldBe 42.0d
11-

ch05/test/src/RegexpCSpec.scala

+7-8
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,16 @@ class RegexpCSpec extends AnyFunSpec:
99
it("matches"):
1010
val txts =
1111
Table(
12-
("txt", "match"),
13-
("Scala", true),
14-
("Scalalalala", true),
15-
("Sca", false),
16-
("Scalal", false),
17-
("Scalaland", false)
12+
("txt", "match"),
13+
("Scala", true),
14+
("Scalalalala", true),
15+
("Sca", false),
16+
("Scalal", false),
17+
("Scalaland", false)
1818
)
1919
// left-associative
2020
val regexp = RegexpC("Sca") ++ RegexpC("la") ++ RegexpC("la").repeat
2121

22-
forAll (txts) { (txt: String, `match`: Boolean) =>
22+
forAll(txts) { (txt: String, `match`: Boolean) =>
2323
regexp.matches(txt) shouldBe `match`
2424
}
25-

ch05/test/src/RegexpSpec.scala

+7-8
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,16 @@ class RegexpSpec extends AnyFunSpec:
99
it("matches"):
1010
val txts =
1111
Table(
12-
("txt", "match"),
13-
("Scala", true),
14-
("Scalalalala", true),
15-
("Sca", false),
16-
("Scalal", false),
17-
("Scalaland", false)
12+
("txt", "match"),
13+
("Scala", true),
14+
("Scalalalala", true),
15+
("Sca", false),
16+
("Scalal", false),
17+
("Scalaland", false)
1818
)
1919
// left-associative
2020
val regexp = Regexp("Sca") ++ Regexp("la") ++ Regexp("la").repeat
2121

22-
forAll (txts) { (txt: String, `match`: Boolean) =>
22+
forAll(txts) { (txt: String, `match`: Boolean) =>
2323
regexp.matches(txt) shouldBe `match`
2424
}
25-

ch05/test/src/RegexpTSpec.scala

-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,3 @@ class RegexpTSpec extends AnyFunSpec:
77
describe("RegexpT"):
88
it("matches"):
99
RegexpT("a").repeat.matches("a" * 20000) shouldBe true
10-

ch06/test/src/CatSpec.scala

+12-12
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ import cats.syntax.show.toShow
66
import cats.syntax.eq.catsSyntaxEq
77

88
class CatSpec extends AnyFunSpec:
9-
describe("Cat"):
10-
it("Show"):
11-
Cat("Garfield", 41, "ginger and black").show shouldBe "Garfield is a 41 year-old ginger and black cat."
9+
describe("Cat"):
10+
it("Show"):
11+
Cat("Garfield", 41, "ginger and black").show shouldBe "Garfield is a 41 year-old ginger and black cat."
1212

13-
it("Eq"):
14-
val cat1 = Cat("Garfield", 38, "orange and black")
15-
val cat2 = Cat("Heathcliff", 32, "orange and black")
13+
it("Eq"):
14+
val cat1 = Cat("Garfield", 38, "orange and black")
15+
val cat2 = Cat("Heathcliff", 32, "orange and black")
1616

17-
cat1 === cat2 shouldBe false
18-
cat1 =!= cat2 shouldBe true
17+
cat1 === cat2 shouldBe false
18+
cat1 =!= cat2 shouldBe true
1919

20-
val optionCat1 = Option(cat1)
21-
val optionCat2 = Option.empty[Cat]
20+
val optionCat1 = Option(cat1)
21+
val optionCat2 = Option.empty[Cat]
2222

23-
optionCat1 === optionCat2 shouldBe false
24-
optionCat1 =!= optionCat2 shouldBe true
23+
optionCat1 === optionCat2 shouldBe false
24+
optionCat1 =!= optionCat2 shouldBe true

ch07/test/src/LibSpec.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ class LibSpec extends AnyFunSpec:
1919

2020
it("should add options"):
2121
val opts = List(Option(22), Option(20))
22-
add(opts) shouldBe Option(42)
22+
add(opts) shouldBe Option(42)

ch08/test/src/TreeSpec.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package ch08
22
import org.scalatest.funspec.AnyFunSpec
33
import org.scalatest.matchers.should.Matchers.shouldBe
4-
import cats.syntax.functor.toFunctorOps // map
4+
import cats.syntax.functor.toFunctorOps // map
55

66
class TreeSpec extends AnyFunSpec:
77
describe("Tree Functor"):
@@ -11,4 +11,4 @@ class TreeSpec extends AnyFunSpec:
1111

1212
it("should map on branch"):
1313
val actual = Tree.branch(Tree.leaf(10), Tree.leaf(20)).map(_ * 2)
14-
actual shouldBe Tree.branch(Tree.leaf(20), Tree.leaf(40))
14+
actual shouldBe Tree.branch(Tree.leaf(20), Tree.leaf(40))

ch09/test/src/LibSpec.scala

+6-6
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import scala.concurrent.{Await, Future}
1111

1212
class LibSpec extends AnyFunSpec:
1313
describe("MonadError"):
14-
it("validateAdult"):
15-
Lib.validateAdult[Try](18).success.value shouldBe 18
16-
Lib.validateAdult[Try](8).failure.exception shouldBe an[IllegalArgumentException]
17-
type ExceptionOr[A] = Either[Throwable, A]
18-
Lib.validateAdult[ExceptionOr](-1).left.value shouldBe an[IllegalArgumentException]
19-
14+
it("validateAdult"):
15+
Lib.validateAdult[Try](18).success.value shouldBe 18
16+
Lib.validateAdult[Try](8).failure.exception shouldBe an[IllegalArgumentException]
17+
type ExceptionOr[A] = Either[Throwable, A]
18+
Lib.validateAdult[ExceptionOr](-1).left.value shouldBe an[IllegalArgumentException]
19+
2020
describe("Writer"):
2121
it("factorial should maintain the order of logging"):
2222
val computations = Future.sequence(

ch09/test/src/TreeSpec.scala

+7-6
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package ch09
22

33
import org.scalatest.funspec.AnyFunSpec
44
import org.scalatest.matchers.should.Matchers.shouldBe
5-
import cats.syntax.flatMap.toFlatMapOps // flatMap
6-
import cats.syntax.functor.toFunctorOps // map
5+
import cats.syntax.flatMap.toFlatMapOps // flatMap
6+
import cats.syntax.functor.toFunctorOps // map
77
import Tree.given
88

99
class TreeSpec extends AnyFunSpec:
1010
describe("Tree monad"):
1111
it("should support flatMap, map, and for-comprehension"):
12-
val actual = Tree.branch(Tree.leaf(100), Tree.leaf(200))
12+
val actual = Tree
13+
.branch(Tree.leaf(100), Tree.leaf(200))
1314
.flatMap(x => Tree.branch(Tree.leaf(x - 1), Tree.leaf(x + 1)))
1415
val expected = Tree.branch(
1516
Tree.branch(Tree.leaf(99), Tree.leaf(101)),
@@ -18,9 +19,9 @@ class TreeSpec extends AnyFunSpec:
1819
actual shouldBe expected
1920

2021
val actual2 = for
21-
a <- Tree.branch(Tree.leaf(100), Tree.leaf(200)) // flatMap
22-
b <- Tree.branch(Tree.leaf(a - 10), Tree.leaf(a + 10)) // flatMap
23-
c <- Tree.branch(Tree.leaf(b - 1), Tree.leaf(b + 1)) // map
22+
a <- Tree.branch(Tree.leaf(100), Tree.leaf(200)) // flatMap
23+
b <- Tree.branch(Tree.leaf(a - 10), Tree.leaf(a + 10)) // flatMap
24+
c <- Tree.branch(Tree.leaf(b - 1), Tree.leaf(b + 1)) // map
2425
yield c
2526
val expected2 = Tree.branch(
2627
Tree.branch(

ch10/src/Lib.scala

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package ch10
2+
3+
import scala.concurrent.Future
4+
import cats.data.EitherT
5+
import scala.concurrent.ExecutionContext.Implicits.global
6+
import scala.concurrent.Await
7+
import scala.concurrent.duration.*
8+
9+
/*
10+
10.4 Exercise: Monads: Transform and Roll Out
11+
12+
The Autobots, well-known robots in disguise, frequently send messages
13+
during battle requesting the power levels of their team mates.
14+
This helps them coordinate strategies and launch devastating attacks.
15+
16+
Transmissions take time in Earth’s viscous atmosphere, and messages are
17+
occasionally lost due to satellite malfunction or sabotage by pesky Decepticons8.
18+
19+
Optimus Prime is getting tired of the nested for comprehensions in his neural matrix.
20+
Help him by rewriting Response using a monad transformer.
21+
*/
22+
object Lib:
23+
24+
type Response[A] = EitherT[Future, String, A]
25+
26+
val powerLevels = Map(
27+
"Jazz" -> 6,
28+
"Bumblebee" -> 8,
29+
"Hot Rod" -> 10
30+
)
31+
32+
/*
33+
Implement getPowerLevel to retrieve data from a set of imaginary allies.
34+
If an Autobot isn’t in the powerLevels map, return an error message reporting
35+
that they were unreachable. Include the name in the message for good effect.
36+
*/
37+
def getPowerLevel(ally: String): Response[Int] =
38+
powerLevels.get(ally) match
39+
case Some(avg) => EitherT.right(Future(avg))
40+
case None => EitherT.left(Future(s"$ally unreachable"))
41+
42+
/*
43+
Two autobots can perform a special move if their combined power level is greater than 15.
44+
If either ally is unavailable, fail with an appropriate error message.
45+
*/
46+
def canSpecialMove(ally1: String, ally2: String): Response[Boolean] =
47+
for
48+
lvl1 <- getPowerLevel(ally1)
49+
lvl2 <- getPowerLevel(ally2)
50+
yield (lvl1 + lvl2) > 15
51+
52+
/*
53+
Write a method tacticalReport that takes two ally names and prints
54+
a message saying whether they can perform a special move.
55+
*/
56+
def tacticalReport(ally1: String, ally2: String): String =
57+
val stack: Future[Either[String, Boolean]] =
58+
canSpecialMove(ally1, ally2).value
59+
60+
Await.result(stack, 1.second) match
61+
case Left(msg) => s"Comms error: $msg"
62+
case Right(true) => s"$ally1 and $ally2 are ready to roll out!"
63+
case Right(false) => s"$ally1 and $ally2 need a recharge."

ch10/src/ch10.worksheet.sc

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import cats.data.{EitherT, OptionT, Writer}
2+
import cats.syntax.applicative.catsSyntaxApplicativeId // pure
3+
import scala.concurrent.ExecutionContext.Implicits.global
4+
import scala.concurrent.duration.*
5+
import scala.concurrent.{Await, Future}
6+
7+
// 10.2 A Transformative Example
8+
/*
9+
we build ListOption from the inside out: we pass List, the type of the outer monad,
10+
as a parameter to OptionT, the transformer for the inner monad.
11+
*/
12+
type ListOption[A] = OptionT[List, A]
13+
14+
// We can create instances of ListOption using the OptionT constructor, or more conveniently using pure
15+
val result1: ListOption[Int] = OptionT(List(Option(10)))
16+
val result2: ListOption[Int] = 32.pure[ListOption]
17+
18+
result1.flatMap { (x: Int) =>
19+
result2.map { (y: Int) =>
20+
x + y
21+
}
22+
}
23+
24+
// 10.3.2 Building Monad Stacks
25+
26+
// Alias Either to a type constructor with one parameter
27+
type ErrorOr[A] = Either[String, A]
28+
29+
// Build our final monad stack using OptionT
30+
type ErrorOrOption[A] = OptionT[ErrorOr, A]
31+
32+
val a = 10.pure[ErrorOrOption]
33+
val b = 32.pure[ErrorOrOption]
34+
35+
val c = a.flatMap(x => b.map(y => x + y))
36+
37+
type FutureEither[A] = EitherT[Future, String, A]
38+
39+
type FutureEitherOption[A] = OptionT[FutureEither, A]
40+
41+
val futureEitherOr: FutureEitherOption[Int] =
42+
for
43+
a <- 10.pure[FutureEitherOption]
44+
b <- 32.pure[FutureEitherOption]
45+
yield a + b
46+
47+
// 10.3.3 Constructing and Unpacking Instances
48+
val intermediate = futureEitherOr.value
49+
50+
val stack = intermediate.value
51+
52+
Await.result(stack, 1.second)
53+
54+
// 10.3.5 Usage Patterns
55+
type Logged[A] = Writer[List[String], A]
56+
57+
// Methods generally return untransformed stacks
58+
def parseNumber(str: String): Logged[Option[Int]] =
59+
util.Try(str.toInt).toOption match
60+
case Some(num) => Writer(List(s"Read $str"), Some(num))
61+
case None => Writer(List(s"Failed on $str"), None)
62+
63+
// Consumers use monad transformers locally to simplify composition
64+
def addAll(a: String, b: String, c: String): Logged[Option[Int]] =
65+
val result = for
66+
a <- OptionT(parseNumber(a))
67+
b <- OptionT(parseNumber(b))
68+
c <- OptionT(parseNumber(c))
69+
yield a + b + c
70+
71+
result.value
72+
73+
// This approach doesn't force OptionT on other users' code:
74+
addAll("1", "2", "3")
75+
addAll("1", "a", "3")

0 commit comments

Comments
 (0)