Skip to content

Commit a39c8b7

Browse files
author
Abhijit Sarkar
committed
Complete ch02
1 parent c5ce581 commit a39c8b7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+285
-1785
lines changed

.github/run.sh

+10-7
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ no_lint=0
88
while (( $# > 0 )); do
99
case "$1" in
1010
--help)
11-
printf "run.sh [OPTION]... [PKG]\n"
11+
printf "run.sh [OPTION]... [DIR]\n"
1212
printf "options:\n"
1313
printf "\t--help Show help\n"
1414
printf "\t--no-test Skip tests\n"
@@ -29,21 +29,24 @@ while (( $# > 0 )); do
2929
esac
3030
done
3131

32+
./mill __.compile
33+
3234
if (( no_test == 0 )); then
3335
if [[ -z "$1" ]]; then
34-
sbt test
36+
./mill __.test
37+
elif ./mill resolve modules["$1"].__.test &>/dev/null; then
38+
./mill modules["$1"].__.test
3539
else
36-
green='\033[1;32m'
40+
red='\033[0;31m'
3741
no_color='\033[0m'
38-
printf "Running tests in packages matching: %b%s*%b\n" "$green" "$1" "$no_color"
39-
sbt "Test / testOnly $1*"
42+
printf "%bNo tests found in: %s%b\n" "$red" "$1" "$no_color"
4043
fi
4144
fi
4245

4346
if (( no_lint == 0 )); then
4447
if [[ -z "${CI}" ]]; then
45-
sbt scalafmtAll
48+
./mill mill.scalalib.scalafmt.ScalafmtModule/reformatAll modules[_].sources
4649
else
47-
sbt scalafmtCheckAll
50+
./mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll modules[_].sources
4851
fi
4952
fi

.github/workflows/ci.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ jobs:
88
runs-on: ubuntu-latest
99

1010
steps:
11-
- uses: actions/checkout@v3
11+
- uses: actions/checkout@v4
1212
- name: Set up JDK
13-
uses: actions/setup-java@v3
13+
uses: actions/setup-java@v4
1414
with:
1515
distribution: temurin
16-
java-version: 17
16+
java-version: 21
1717
- name: Test
1818
run: ./.github/run.sh --no-lint
1919
- name: Lint

.gitignore

+13-26
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,20 @@
1-
# macOS
21
.DS_Store
2+
*.class
3+
*.log
4+
*~
35

4-
# sbt
5-
dist/*
6-
target/
7-
lib_managed/
8-
src_managed/
9-
project/boot/
10-
project/project
11-
project/plugins/project/
12-
project/local-plugins.sbt
13-
.history
14-
.ensime
15-
.ensime_cache/
16-
.sbt-scripted/
17-
local.sbt
6+
# idea
7+
.idea
8+
.idea_modules
9+
/.worksheet/
1810

19-
# Bloop
20-
.bsp
11+
# mill
12+
out/
2113

22-
# VS Code
14+
.bsp/
15+
.metals/
2316
.vscode/
24-
25-
# Metals
2617
.bloop/
27-
.metals/
28-
metals.sbt
2918

30-
# IDEA
31-
.idea
32-
.idea_modules
33-
/.worksheet/
19+
# virtual machine crash logs
20+
hs_err_pid*

.mill-version

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.12.3

.scalafmt.conf

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version = "3.7.17"
1+
version = "3.8.3"
22
align.preset = more
33
maxColumn = 120
44
runner.dialect = scala3

README.md

+4-19
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,14 @@ My solutions to the exercises from the book [Scala with Cats](https://www.scalaw
44

55
Official GitHub repo: https://github.com/scalawithcats/scala-with-cats
66

7-
## Contents
7+
The main branch corresponds to the latest edition of the book (currently 2nd).
8+
The older code is available in branches.
89

9-
1. [Introduction](src/main/scala/ch01)
10-
2. [Monoids and Semigroups](src/main/scala/ch02)
11-
3. [Functors](src/main/scala/ch03)
12-
4. [Monads](src/main/scala/ch04)
13-
5. [Monad Transformers](src/main/scala/ch05)
14-
6. [Semigroupal and Applicative](src/main/scala/ch06)
15-
7. [Foldable and Traverse](src/main/scala/ch07)
16-
8. [Case Study: Testing Asynchronous Code](src/main/scala/ch08)
17-
9. [Case Study: Map-Reduce](src/main/scala/ch09)
18-
10. [Case Study: Data Validation](src/main/scala/ch10)
19-
11. [Case Study: CRDTs](src/main/scala/ch11)
10+
## Contents
2011

2112
## Running tests
22-
23-
```
24-
./.github/run.sh
25-
```
26-
27-
To run all tests from a package:
2813
```
29-
./.github/run.sh <package prefix>
14+
./.github/run.sh <directory>
3015
```
3116

3217
## License

build.mill

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package build
2+
3+
import mill._, scalalib._, scalafmt._
4+
import $packages._
5+
import $file.{versions => v}
6+
7+
def isBookChapter(p: os.Path) = os.isDir(p) && "ch\\d{2}".r.matches(p.baseName)
8+
9+
def moduleNames = interp.watchValue(
10+
os.walk(millSourcePath, !isBookChapter(_), maxDepth = 1)
11+
.map(_.last)
12+
)
13+
14+
object modules extends Cross[CatsModule](moduleNames)
15+
16+
trait CatsModule extends ScalaModule with Cross.Module[String] with ScalafmtModule {
17+
18+
val scalaVersion = v.scalaVersion
19+
20+
// Ends with 'modules' that need to be removed
21+
def millSourcePath = super.millSourcePath / os.up / crossValue
22+
23+
def scalacOptions: T[Seq[String]] = Seq(
24+
"-encoding", "UTF-8",
25+
"-feature",
26+
"-Werror",
27+
"-explain",
28+
"-deprecation",
29+
"-unchecked",
30+
"-Wunused:all",
31+
"-rewrite",
32+
"-indent",
33+
"-source", "future",
34+
)
35+
36+
object test extends ScalaTests with TestModule.ScalaTest {
37+
val commonDeps = Seq(
38+
ivy"org.scalatest::scalatest:${v.scalatestVersion}",
39+
ivy"org.scalatestplus::scalacheck-1-18:${v.scalacheckVersion}"
40+
)
41+
42+
def ivyDeps = Task{commonDeps}
43+
}
44+
}

build.sbt

-33
This file was deleted.

ch02/src/MyList.scala

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package ch02
2+
3+
/*
4+
Structural corecursion works by considering all the possible outputs, which are the constructors of
5+
the algebraic data type, and then working out the conditions under which we’d call each constructor.
6+
7+
We could abstract structural corecursion as an unfold.
8+
*/
9+
enum MyList[A]:
10+
case Empty()
11+
case Pair(_head: A, _tail: MyList[A])
12+
13+
def map[B](f: A => B): MyList[B] =
14+
// this match
15+
// case Empty() => Empty()
16+
// case Pair(head, tail) => Pair(f(head), tail.map(f))
17+
MyList.unfold(this)(_.isEmpty, a => f(a.head), _.tail)
18+
19+
def isEmpty: Boolean =
20+
this match
21+
case Empty() => true
22+
case _ => false
23+
24+
def head: A =
25+
this match
26+
case Pair(head, _) => head
27+
case _ => scala.sys.error("empty list")
28+
29+
def tail: MyList[A] =
30+
this match
31+
case Pair(_, tail) => tail
32+
case _ => scala.sys.error("empty list")
33+
34+
def fold[B](zero: B)(f: (A, B) => B): B =
35+
this match
36+
case Empty() => zero
37+
case Pair(head, tail) => f(head, tail.fold(zero)(f))
38+
39+
def toSeq: Seq[A] = fold(Seq.empty)(_ +: _)
40+
41+
object MyList:
42+
/*
43+
Types inferred for one method parameter cannot be used for other method parameters in the same parameter list.
44+
However, types inferred for one method parameter list can be used in subsequent lists.
45+
*/
46+
def unfold[A, B](seed: A)(stop: A => Boolean, f: A => B, next: A => A): MyList[B] =
47+
if stop(seed) then MyList.Empty()
48+
else MyList.Pair(f(seed), unfold(next(seed))(stop, f, next))
49+
50+
def fill[A](n: Int)(elem: => A): MyList[A] =
51+
unfold(0)(_ == n, _ => elem, _ + 1)
52+
53+
def iterate[A](start: A, len: Int)(f: A => A): MyList[A] =
54+
unfold((0, start))(_._1 == len, _._2, (i, a) => (i + 1, f(a)))

ch02/src/Tree.scala

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package ch02
2+
3+
/*
4+
Structural recursion works by considering all the possible inputs (which we usually represent as patterns),
5+
and then working out what we do with each input case.
6+
7+
We could abstract structural recursion as an fold.
8+
*/
9+
enum Tree[A]:
10+
case Leaf(value: A)
11+
case Node(left: Tree[A], right: Tree[A])
12+
13+
/** @return
14+
* the number of values (Leafs) stored in the Tree
15+
*/
16+
def size: Int = this.fold(_ => 1, _ + _)
17+
// this match
18+
// case Tree.Leaf(value) => 1
19+
// case Tree.Node(left, right) => left.size + right.size
20+
21+
/** @param elem
22+
* element to look for
23+
* @return
24+
* true if the Tree contains a given element of type A, and false otherwise
25+
*/
26+
def contains(elem: A): Boolean = this.fold(_ == elem, _ || _)
27+
// this match
28+
// case Tree.Leaf(value) => value == elem
29+
// case Tree.Node(left, right) => left.contains(elem) || right.contains(elem)
30+
31+
/** @param f
32+
* value transformation function
33+
* @return
34+
* a Tree[B] given a function A => B
35+
*/
36+
def map[B](f: A => B): Tree[B] = this.fold(v => Tree.Leaf(f(v)), (l, r) => Tree.Node(l, r))
37+
// this match
38+
// case Tree.Leaf(value) => Tree.Leaf(f(value))
39+
// case Tree.Node(left, right) => Tree.Node(left.map(f), right.map(f))
40+
41+
def fold[B](f: (A => B), g: (B, B) => B): B =
42+
this match
43+
case Tree.Node(left, right) => g(left.fold(f, g), right.fold(f, g))
44+
case Tree.Leaf(value) => f(value)

ch02/test/src/MyListSpec.scala

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package ch02
2+
3+
import org.scalatest.funspec.AnyFunSpec
4+
import org.scalatest.matchers.should.Matchers.shouldBe
5+
6+
class MyListSpec extends AnyFunSpec:
7+
describe("fill"):
8+
it("stateless"):
9+
val actual = MyList.fill(5)(1)
10+
actual.toSeq shouldBe Seq.fill(5)(1)
11+
12+
it("stateful"):
13+
var counter = 0
14+
15+
def getAndInc(): Int =
16+
val temp = counter
17+
counter = counter + 1
18+
temp
19+
20+
val actual = MyList.fill(5)(getAndInc())
21+
actual.toSeq shouldBe (0 to 4)
22+
counter shouldBe 5
23+
24+
describe("iterate"):
25+
it("decrement"):
26+
val actual = MyList.iterate(0, 5)(_ - 1)
27+
actual.toSeq shouldBe (0 to -4 by -1)
28+
29+
describe("map"):
30+
it("evens"):
31+
val actual = MyList.iterate(0, 5)(_ + 1).map(_ * 2)
32+
actual.toSeq shouldBe (0 to 8 by 2)
33+
34+
35+
36+
37+
38+
39+

0 commit comments

Comments
 (0)