Skip to content

Commit

Permalink
Complete ch02
Browse files Browse the repository at this point in the history
  • Loading branch information
Abhijit Sarkar committed Dec 18, 2024
1 parent c5ce581 commit a39c8b7
Show file tree
Hide file tree
Showing 61 changed files with 285 additions and 1,785 deletions.
17 changes: 10 additions & 7 deletions .github/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ no_lint=0
while (( $# > 0 )); do
case "$1" in
--help)
printf "run.sh [OPTION]... [PKG]\n"
printf "run.sh [OPTION]... [DIR]\n"
printf "options:\n"
printf "\t--help Show help\n"
printf "\t--no-test Skip tests\n"
Expand All @@ -29,21 +29,24 @@ while (( $# > 0 )); do
esac
done

./mill __.compile

if (( no_test == 0 )); then
if [[ -z "$1" ]]; then
sbt test
./mill __.test
elif ./mill resolve modules["$1"].__.test &>/dev/null; then
./mill modules["$1"].__.test
else
green='\033[1;32m'
red='\033[0;31m'
no_color='\033[0m'
printf "Running tests in packages matching: %b%s*%b\n" "$green" "$1" "$no_color"
sbt "Test / testOnly $1*"
printf "%bNo tests found in: %s%b\n" "$red" "$1" "$no_color"
fi
fi

if (( no_lint == 0 )); then
if [[ -z "${CI}" ]]; then
sbt scalafmtAll
./mill mill.scalalib.scalafmt.ScalafmtModule/reformatAll modules[_].sources
else
sbt scalafmtCheckAll
./mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll modules[_].sources
fi
fi
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
java-version: 21
- name: Test
run: ./.github/run.sh --no-lint
- name: Lint
Expand Down
39 changes: 13 additions & 26 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,33 +1,20 @@
# macOS
.DS_Store
*.class
*.log
*~

# sbt
dist/*
target/
lib_managed/
src_managed/
project/boot/
project/project
project/plugins/project/
project/local-plugins.sbt
.history
.ensime
.ensime_cache/
.sbt-scripted/
local.sbt
# idea
.idea
.idea_modules
/.worksheet/

# Bloop
.bsp
# mill
out/

# VS Code
.bsp/
.metals/
.vscode/

# Metals
.bloop/
.metals/
metals.sbt

# IDEA
.idea
.idea_modules
/.worksheet/
# virtual machine crash logs
hs_err_pid*
1 change: 1 addition & 0 deletions .mill-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.12.3
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = "3.7.17"
version = "3.8.3"
align.preset = more
maxColumn = 120
runner.dialect = scala3
Expand Down
23 changes: 4 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,14 @@ My solutions to the exercises from the book [Scala with Cats](https://www.scalaw

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

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

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

## Running tests

```
./.github/run.sh
```

To run all tests from a package:
```
./.github/run.sh <package prefix>
./.github/run.sh <directory>
```

## License
Expand Down
44 changes: 44 additions & 0 deletions build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package build

import mill._, scalalib._, scalafmt._
import $packages._
import $file.{versions => v}

def isBookChapter(p: os.Path) = os.isDir(p) && "ch\\d{2}".r.matches(p.baseName)

def moduleNames = interp.watchValue(
os.walk(millSourcePath, !isBookChapter(_), maxDepth = 1)
.map(_.last)
)

object modules extends Cross[CatsModule](moduleNames)

trait CatsModule extends ScalaModule with Cross.Module[String] with ScalafmtModule {

val scalaVersion = v.scalaVersion

// Ends with 'modules' that need to be removed
def millSourcePath = super.millSourcePath / os.up / crossValue

def scalacOptions: T[Seq[String]] = Seq(
"-encoding", "UTF-8",
"-feature",
"-Werror",
"-explain",
"-deprecation",
"-unchecked",
"-Wunused:all",
"-rewrite",
"-indent",
"-source", "future",
)

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

def ivyDeps = Task{commonDeps}
}
}
33 changes: 0 additions & 33 deletions build.sbt

This file was deleted.

54 changes: 54 additions & 0 deletions ch02/src/MyList.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package ch02

/*
Structural corecursion works by considering all the possible outputs, which are the constructors of
the algebraic data type, and then working out the conditions under which we’d call each constructor.
We could abstract structural corecursion as an unfold.
*/
enum MyList[A]:
case Empty()
case Pair(_head: A, _tail: MyList[A])

def map[B](f: A => B): MyList[B] =
// this match
// case Empty() => Empty()
// case Pair(head, tail) => Pair(f(head), tail.map(f))
MyList.unfold(this)(_.isEmpty, a => f(a.head), _.tail)

def isEmpty: Boolean =
this match
case Empty() => true
case _ => false

def head: A =
this match
case Pair(head, _) => head
case _ => scala.sys.error("empty list")

def tail: MyList[A] =
this match
case Pair(_, tail) => tail
case _ => scala.sys.error("empty list")

def fold[B](zero: B)(f: (A, B) => B): B =
this match
case Empty() => zero
case Pair(head, tail) => f(head, tail.fold(zero)(f))

def toSeq: Seq[A] = fold(Seq.empty)(_ +: _)

object MyList:
/*
Types inferred for one method parameter cannot be used for other method parameters in the same parameter list.
However, types inferred for one method parameter list can be used in subsequent lists.
*/
def unfold[A, B](seed: A)(stop: A => Boolean, f: A => B, next: A => A): MyList[B] =
if stop(seed) then MyList.Empty()
else MyList.Pair(f(seed), unfold(next(seed))(stop, f, next))

def fill[A](n: Int)(elem: => A): MyList[A] =
unfold(0)(_ == n, _ => elem, _ + 1)

def iterate[A](start: A, len: Int)(f: A => A): MyList[A] =
unfold((0, start))(_._1 == len, _._2, (i, a) => (i + 1, f(a)))
44 changes: 44 additions & 0 deletions ch02/src/Tree.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package ch02

/*
Structural recursion works by considering all the possible inputs (which we usually represent as patterns),
and then working out what we do with each input case.
We could abstract structural recursion as an fold.
*/
enum Tree[A]:
case Leaf(value: A)
case Node(left: Tree[A], right: Tree[A])

/** @return
* the number of values (Leafs) stored in the Tree
*/
def size: Int = this.fold(_ => 1, _ + _)
// this match
// case Tree.Leaf(value) => 1
// case Tree.Node(left, right) => left.size + right.size

/** @param elem
* element to look for
* @return
* true if the Tree contains a given element of type A, and false otherwise
*/
def contains(elem: A): Boolean = this.fold(_ == elem, _ || _)
// this match
// case Tree.Leaf(value) => value == elem
// case Tree.Node(left, right) => left.contains(elem) || right.contains(elem)

/** @param f
* value transformation function
* @return
* a Tree[B] given a function A => B
*/
def map[B](f: A => B): Tree[B] = this.fold(v => Tree.Leaf(f(v)), (l, r) => Tree.Node(l, r))
// this match
// case Tree.Leaf(value) => Tree.Leaf(f(value))
// case Tree.Node(left, right) => Tree.Node(left.map(f), right.map(f))

def fold[B](f: (A => B), g: (B, B) => B): B =
this match
case Tree.Node(left, right) => g(left.fold(f, g), right.fold(f, g))
case Tree.Leaf(value) => f(value)
39 changes: 39 additions & 0 deletions ch02/test/src/MyListSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package ch02

import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers.shouldBe

class MyListSpec extends AnyFunSpec:
describe("fill"):
it("stateless"):
val actual = MyList.fill(5)(1)
actual.toSeq shouldBe Seq.fill(5)(1)

it("stateful"):
var counter = 0

def getAndInc(): Int =
val temp = counter
counter = counter + 1
temp

val actual = MyList.fill(5)(getAndInc())
actual.toSeq shouldBe (0 to 4)
counter shouldBe 5

describe("iterate"):
it("decrement"):
val actual = MyList.iterate(0, 5)(_ - 1)
actual.toSeq shouldBe (0 to -4 by -1)

describe("map"):
it("evens"):
val actual = MyList.iterate(0, 5)(_ + 1).map(_ * 2)
actual.toSeq shouldBe (0 to 8 by 2)







Loading

0 comments on commit a39c8b7

Please sign in to comment.