Skip to content

Commit

Permalink
Complete till ch12
Browse files Browse the repository at this point in the history
  • Loading branch information
Abhijit Sarkar committed Dec 26, 2024
1 parent eb1bc3e commit d684f6c
Show file tree
Hide file tree
Showing 26 changed files with 607 additions and 104 deletions.
4 changes: 2 additions & 2 deletions .github/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ fi

if (( no_lint == 0 )); then
if [[ -z "${CI}" ]]; then
./mill mill.scalalib.scalafmt.ScalafmtModule/reformatAll modules[_].sources
./mill mill.scalalib.scalafmt.ScalafmtModule/reformatAll modules[_].__.sources
else
./mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll modules[_].sources
./mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll modules[_].__.sources
fi
fi
2 changes: 1 addition & 1 deletion .mill-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.12.3
0.12.4
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ The older code is available in branches.
7. [Monoids and Semigroups](ch07)
8. [Functors](ch08)
9. [Monads](ch09)
10. [Monad Transformers](ch10)
11. [Semigroupal and Applicative](ch11)
12. [Foldable and Traverse](ch12)

## Running tests
```
Expand Down
4 changes: 1 addition & 3 deletions build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,9 @@ trait CatsModule extends ScalaModule with Cross.Module[String] with ScalafmtModu
)

object test extends ScalaTests with TestModule.ScalaTest {
val commonDeps = Seq(
def ivyDeps = Agg(
ivy"org.scalatest::scalatest:${v.scalatestVersion}",
ivy"org.scalatestplus::scalacheck-1-18:${v.scalacheckVersion}"
)

def ivyDeps = Task{commonDeps}
}
}
7 changes: 0 additions & 7 deletions ch02/test/src/MyListSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,3 @@ class MyListSpec extends AnyFunSpec:
it("evens"):
val actual = MyList.iterate(0, 5)(_ + 1).map(_ * 2)
actual.toSeq shouldBe (0 to 8 by 2)







1 change: 0 additions & 1 deletion ch03/test/src/BoolSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,3 @@ class BoolSpec extends AnyFunSpec:
it("not"):
not(True).`if`("yes")("no") shouldBe "no"
not(False).`if`("yes")("no") shouldBe "yes"

1 change: 0 additions & 1 deletion ch03/test/src/CodataSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,3 @@ class CodataSpec extends AnyFunSpec:

val product = list()(1, (a, b) => a * b)
product shouldBe 6

11 changes: 5 additions & 6 deletions ch03/test/src/SetSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ class SetSpec extends AnyFunSpec:
Evens.union(ListSet.empty.insert(1).insert(3))

evensAndOne.contains(1) shouldBe true
evensAndOthers.contains(1) shouldBe true
evensAndOne.contains(2) shouldBe true
evensAndOthers.contains(2) shouldBe true
evensAndOne.contains(3) shouldBe false
evensAndOthers.contains(3) shouldBe true

evensAndOthers.contains(1) shouldBe true
evensAndOne.contains(2) shouldBe true
evensAndOthers.contains(2) shouldBe true
evensAndOne.contains(3) shouldBe false
evensAndOthers.contains(3) shouldBe true
3 changes: 0 additions & 3 deletions ch03/test/src/StreamSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,3 @@ class StreamSpec extends AnyFunSpec:
Stream.naturals.take(5) shouldBe (1 to 5)
Stream.naturals2.take(5) shouldBe (1 to 5)
Stream.naturals3.take(5) shouldBe (1 to 5)



1 change: 0 additions & 1 deletion ch05/test/src/ExpressionCSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@ class ExpressionCSpec extends AnyFunSpec:
it("eval"):
val fortyTwo = ((ExpressionC(15.0) + ExpressionC(5.0)) * ExpressionC(2.0) + ExpressionC(2.0)) / ExpressionC(1.0)
fortyTwo.eval shouldBe 42.0d

1 change: 0 additions & 1 deletion ch05/test/src/ExpressionSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@ class ExpressionSpec extends AnyFunSpec:
it("eval"):
val fortyTwo = ((Expression(15.0) + Expression(5.0)) * Expression(2.0) + Expression(2.0)) / Expression(1.0)
fortyTwo.eval shouldBe 42.0d

1 change: 0 additions & 1 deletion ch05/test/src/ExpressionTSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@ class ExpressionTSpec extends AnyFunSpec:
it("eval"):
val fortyTwo = ((ExpressionT(15.0) + ExpressionT(5.0)) * ExpressionT(2.0) + ExpressionT(2.0)) / ExpressionT(1.0)
fortyTwo.eval shouldBe 42.0d

15 changes: 7 additions & 8 deletions ch05/test/src/RegexpCSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,16 @@ class RegexpCSpec extends AnyFunSpec:
it("matches"):
val txts =
Table(
("txt", "match"),
("Scala", true),
("Scalalalala", true),
("Sca", false),
("Scalal", false),
("Scalaland", false)
("txt", "match"),
("Scala", true),
("Scalalalala", true),
("Sca", false),
("Scalal", false),
("Scalaland", false)
)
// left-associative
val regexp = RegexpC("Sca") ++ RegexpC("la") ++ RegexpC("la").repeat

forAll (txts) { (txt: String, `match`: Boolean) =>
forAll(txts) { (txt: String, `match`: Boolean) =>
regexp.matches(txt) shouldBe `match`
}

15 changes: 7 additions & 8 deletions ch05/test/src/RegexpSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,16 @@ class RegexpSpec extends AnyFunSpec:
it("matches"):
val txts =
Table(
("txt", "match"),
("Scala", true),
("Scalalalala", true),
("Sca", false),
("Scalal", false),
("Scalaland", false)
("txt", "match"),
("Scala", true),
("Scalalalala", true),
("Sca", false),
("Scalal", false),
("Scalaland", false)
)
// left-associative
val regexp = Regexp("Sca") ++ Regexp("la") ++ Regexp("la").repeat

forAll (txts) { (txt: String, `match`: Boolean) =>
forAll(txts) { (txt: String, `match`: Boolean) =>
regexp.matches(txt) shouldBe `match`
}

1 change: 0 additions & 1 deletion ch05/test/src/RegexpTSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,3 @@ class RegexpTSpec extends AnyFunSpec:
describe("RegexpT"):
it("matches"):
RegexpT("a").repeat.matches("a" * 20000) shouldBe true

24 changes: 12 additions & 12 deletions ch06/test/src/CatSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ import cats.syntax.show.toShow
import cats.syntax.eq.catsSyntaxEq

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

it("Eq"):
val cat1 = Cat("Garfield", 38, "orange and black")
val cat2 = Cat("Heathcliff", 32, "orange and black")
it("Eq"):
val cat1 = Cat("Garfield", 38, "orange and black")
val cat2 = Cat("Heathcliff", 32, "orange and black")

cat1 === cat2 shouldBe false
cat1 =!= cat2 shouldBe true
cat1 === cat2 shouldBe false
cat1 =!= cat2 shouldBe true

val optionCat1 = Option(cat1)
val optionCat2 = Option.empty[Cat]
val optionCat1 = Option(cat1)
val optionCat2 = Option.empty[Cat]

optionCat1 === optionCat2 shouldBe false
optionCat1 =!= optionCat2 shouldBe true
optionCat1 === optionCat2 shouldBe false
optionCat1 =!= optionCat2 shouldBe true
2 changes: 1 addition & 1 deletion ch07/test/src/LibSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ class LibSpec extends AnyFunSpec:

it("should add options"):
val opts = List(Option(22), Option(20))
add(opts) shouldBe Option(42)
add(opts) shouldBe Option(42)
4 changes: 2 additions & 2 deletions ch08/test/src/TreeSpec.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package ch08
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers.shouldBe
import cats.syntax.functor.toFunctorOps // map
import cats.syntax.functor.toFunctorOps // map

class TreeSpec extends AnyFunSpec:
describe("Tree Functor"):
Expand All @@ -11,4 +11,4 @@ class TreeSpec extends AnyFunSpec:

it("should map on branch"):
val actual = Tree.branch(Tree.leaf(10), Tree.leaf(20)).map(_ * 2)
actual shouldBe Tree.branch(Tree.leaf(20), Tree.leaf(40))
actual shouldBe Tree.branch(Tree.leaf(20), Tree.leaf(40))
12 changes: 6 additions & 6 deletions ch09/test/src/LibSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ import scala.concurrent.{Await, Future}

class LibSpec extends AnyFunSpec:
describe("MonadError"):
it("validateAdult"):
Lib.validateAdult[Try](18).success.value shouldBe 18
Lib.validateAdult[Try](8).failure.exception shouldBe an[IllegalArgumentException]
type ExceptionOr[A] = Either[Throwable, A]
Lib.validateAdult[ExceptionOr](-1).left.value shouldBe an[IllegalArgumentException]
it("validateAdult"):
Lib.validateAdult[Try](18).success.value shouldBe 18
Lib.validateAdult[Try](8).failure.exception shouldBe an[IllegalArgumentException]
type ExceptionOr[A] = Either[Throwable, A]
Lib.validateAdult[ExceptionOr](-1).left.value shouldBe an[IllegalArgumentException]

describe("Writer"):
it("factorial should maintain the order of logging"):
val computations = Future.sequence(
Expand Down
13 changes: 7 additions & 6 deletions ch09/test/src/TreeSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package ch09

import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers.shouldBe
import cats.syntax.flatMap.toFlatMapOps // flatMap
import cats.syntax.functor.toFunctorOps // map
import cats.syntax.flatMap.toFlatMapOps // flatMap
import cats.syntax.functor.toFunctorOps // map
import Tree.given

class TreeSpec extends AnyFunSpec:
describe("Tree monad"):
it("should support flatMap, map, and for-comprehension"):
val actual = Tree.branch(Tree.leaf(100), Tree.leaf(200))
val actual = Tree
.branch(Tree.leaf(100), Tree.leaf(200))
.flatMap(x => Tree.branch(Tree.leaf(x - 1), Tree.leaf(x + 1)))
val expected = Tree.branch(
Tree.branch(Tree.leaf(99), Tree.leaf(101)),
Expand All @@ -18,9 +19,9 @@ class TreeSpec extends AnyFunSpec:
actual shouldBe expected

val actual2 = for
a <- Tree.branch(Tree.leaf(100), Tree.leaf(200)) // flatMap
b <- Tree.branch(Tree.leaf(a - 10), Tree.leaf(a + 10)) // flatMap
c <- Tree.branch(Tree.leaf(b - 1), Tree.leaf(b + 1)) // map
a <- Tree.branch(Tree.leaf(100), Tree.leaf(200)) // flatMap
b <- Tree.branch(Tree.leaf(a - 10), Tree.leaf(a + 10)) // flatMap
c <- Tree.branch(Tree.leaf(b - 1), Tree.leaf(b + 1)) // map
yield c
val expected2 = Tree.branch(
Tree.branch(
Expand Down
63 changes: 63 additions & 0 deletions ch10/src/Lib.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package ch10

import scala.concurrent.Future
import cats.data.EitherT
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Await
import scala.concurrent.duration.*

/*
10.4 Exercise: Monads: Transform and Roll Out
The Autobots, well-known robots in disguise, frequently send messages
during battle requesting the power levels of their team mates.
This helps them coordinate strategies and launch devastating attacks.
Transmissions take time in Earth’s viscous atmosphere, and messages are
occasionally lost due to satellite malfunction or sabotage by pesky Decepticons8.
Optimus Prime is getting tired of the nested for comprehensions in his neural matrix.
Help him by rewriting Response using a monad transformer.
*/
object Lib:

type Response[A] = EitherT[Future, String, A]

val powerLevels = Map(
"Jazz" -> 6,
"Bumblebee" -> 8,
"Hot Rod" -> 10
)

/*
Implement getPowerLevel to retrieve data from a set of imaginary allies.
If an Autobot isn’t in the powerLevels map, return an error message reporting
that they were unreachable. Include the name in the message for good effect.
*/
def getPowerLevel(ally: String): Response[Int] =
powerLevels.get(ally) match
case Some(avg) => EitherT.right(Future(avg))
case None => EitherT.left(Future(s"$ally unreachable"))

/*
Two autobots can perform a special move if their combined power level is greater than 15.
If either ally is unavailable, fail with an appropriate error message.
*/
def canSpecialMove(ally1: String, ally2: String): Response[Boolean] =
for
lvl1 <- getPowerLevel(ally1)
lvl2 <- getPowerLevel(ally2)
yield (lvl1 + lvl2) > 15

/*
Write a method tacticalReport that takes two ally names and prints
a message saying whether they can perform a special move.
*/
def tacticalReport(ally1: String, ally2: String): String =
val stack: Future[Either[String, Boolean]] =
canSpecialMove(ally1, ally2).value

Await.result(stack, 1.second) match
case Left(msg) => s"Comms error: $msg"
case Right(true) => s"$ally1 and $ally2 are ready to roll out!"
case Right(false) => s"$ally1 and $ally2 need a recharge."
75 changes: 75 additions & 0 deletions ch10/src/ch10.worksheet.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import cats.data.{EitherT, OptionT, Writer}
import cats.syntax.applicative.catsSyntaxApplicativeId // pure
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.*
import scala.concurrent.{Await, Future}

// 10.2 A Transformative Example
/*
we build ListOption from the inside out: we pass List, the type of the outer monad,
as a parameter to OptionT, the transformer for the inner monad.
*/
type ListOption[A] = OptionT[List, A]

// We can create instances of ListOption using the OptionT constructor, or more conveniently using pure
val result1: ListOption[Int] = OptionT(List(Option(10)))
val result2: ListOption[Int] = 32.pure[ListOption]

result1.flatMap { (x: Int) =>
result2.map { (y: Int) =>
x + y
}
}

// 10.3.2 Building Monad Stacks

// Alias Either to a type constructor with one parameter
type ErrorOr[A] = Either[String, A]

// Build our final monad stack using OptionT
type ErrorOrOption[A] = OptionT[ErrorOr, A]

val a = 10.pure[ErrorOrOption]
val b = 32.pure[ErrorOrOption]

val c = a.flatMap(x => b.map(y => x + y))

type FutureEither[A] = EitherT[Future, String, A]

type FutureEitherOption[A] = OptionT[FutureEither, A]

val futureEitherOr: FutureEitherOption[Int] =
for
a <- 10.pure[FutureEitherOption]
b <- 32.pure[FutureEitherOption]
yield a + b

// 10.3.3 Constructing and Unpacking Instances
val intermediate = futureEitherOr.value

val stack = intermediate.value

Await.result(stack, 1.second)

// 10.3.5 Usage Patterns
type Logged[A] = Writer[List[String], A]

// Methods generally return untransformed stacks
def parseNumber(str: String): Logged[Option[Int]] =
util.Try(str.toInt).toOption match
case Some(num) => Writer(List(s"Read $str"), Some(num))
case None => Writer(List(s"Failed on $str"), None)

// Consumers use monad transformers locally to simplify composition
def addAll(a: String, b: String, c: String): Logged[Option[Int]] =
val result = for
a <- OptionT(parseNumber(a))
b <- OptionT(parseNumber(b))
c <- OptionT(parseNumber(c))
yield a + b + c

result.value

// This approach doesn't force OptionT on other users' code:
addAll("1", "2", "3")
addAll("1", "a", "3")
Loading

0 comments on commit d684f6c

Please sign in to comment.