Skip to content

Commit 5b6d30d

Browse files
author
Abhijit Sarkar
committed
Complete ch08
1 parent 045f097 commit 5b6d30d

7 files changed

+217
-1
lines changed

.scalafmt.conf

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ assumeStandardLibraryStripMargin = true
77
project.excludePaths = [
88
"glob:**/ch04/src/**.scala",
99
"glob:**/ch06/src/Cat.scala",
10-
"glob:**/ch07/src/*.scala"
10+
"glob:**/ch07/src/*.scala",
11+
"glob:**/ch08/src/*.scala"
1112
]

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ The older code is available in branches.
1515
5. [Reified Interpreters](ch05)
1616
6. [Using Cats](ch06)
1717
7. [Monoids and Semigroups](ch07)
18+
8. [Functors](ch08)
1819

1920
## Running tests
2021
```

ch08/src/Codec.scala

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package ch08
2+
3+
/*
4+
8.6.2.1 Transformative Thinking with imap
5+
Implement the imap method for Codec.
6+
*/
7+
trait Codec[A]:
8+
def encode(value: A): String
9+
def decode(value: String): A
10+
def imap[B](dec: A => B, enc: B => A): Codec[B] =
11+
val self = this
12+
new Codec[B]:
13+
override def encode(value: B): String =
14+
self.encode(enc(value))
15+
16+
override def decode(value: String): B =
17+
dec(self.decode(value))
18+
19+
def encode[A : Codec as c](value: A): String =
20+
c.encode(value)
21+
22+
def decode[A : Codec as c](value: String): A =
23+
c.decode(value)
24+
25+
object Codec:
26+
final case class Box[A](value: A)
27+
28+
given stringCodec: Codec[String]:
29+
def encode(value: String): String = value
30+
def decode(value: String): String = value
31+
32+
given Codec[Int] =
33+
stringCodec.imap(_.toInt, _.toString)
34+
35+
// Demonstrate your imap method works by creating a Codec for Double.
36+
given Codec[Double] =
37+
stringCodec.imap(_.toDouble, _.toString)
38+
39+
// Implement a Codec for the Box type.
40+
given boxCodec: [A : Codec as c] => Codec[Box[A]] =
41+
c.imap[Box[A]](Box(_), _.value)

ch08/src/Display.scala

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package ch08
2+
3+
/*
4+
8.6.1.1 Exercise: Showing off with Contramap
5+
Implement the contramap method for Display above.
6+
*/
7+
trait Display[A]:
8+
def display(value: A): String
9+
10+
def contramap[B](f: B => A): Display[B] =
11+
val self = this
12+
new Display[B]:
13+
def display(value: B): String =
14+
self.display(f(value))
15+
16+
def display[A: Display as d](value: A): String =
17+
d.display(value)
18+
19+
object Display:
20+
final case class Box[A](value: A)
21+
22+
given Display[String]:
23+
def display(value: String): String =
24+
s"'${value}'"
25+
26+
given Display[Boolean]:
27+
def display(value: Boolean): String =
28+
if (value) "yes" else "no"
29+
30+
/*
31+
Define an instance of Display for the following Box case class.
32+
Rather than writing out the complete definition from scratch
33+
(new Display[Box] etc...), create your instance from an existing
34+
instance using contramap.
35+
*/
36+
// Given a Display[A], and a Box[A], we can create
37+
// Display[Box[A]] by the reverse mapping Box[A] => A.
38+
// The forward mapping would be A => Box[A].
39+
40+
// Parameterized typeclass with named context bound.
41+
given [A : Display as d] => Display[Box[A]] =
42+
d.contramap[Box[A]](_.value)

ch08/src/Tree.scala

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package ch08
2+
3+
import cats.Functor
4+
5+
sealed trait Tree[+A]
6+
7+
final case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]
8+
9+
final case class Leaf[A](value: A) extends Tree[A]
10+
11+
object Tree:
12+
/* 8.5.4 Exercise: Branching out with Functors
13+
Write a Functor for the following binary tree data type.
14+
Verify that the code works as expected on instances of Branch and Leaf.
15+
*/
16+
given Functor[Tree]:
17+
override def map[A, B](fa: Tree[A])(f: A => B): Tree[B] =
18+
fa match
19+
case Branch(l, r) => Branch(map(l)(f), map(r)(f))
20+
case Leaf(value) => Leaf(f(value))
21+
22+
// The compiler can find a Functor instance for Tree but not for Branch or Leaf (Functor is invariant in F).
23+
// Let's add some smart constructors to compensate.
24+
def branch[A](left: Tree[A], right: Tree[A]): Tree[A] =
25+
Branch(left, right)
26+
27+
def leaf[A](value: A): Tree[A] =
28+
Leaf(value)

ch08/src/ch08.worksheet.sc

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import ch08.{display, Display, encode, decode, Codec}
2+
3+
display("hello")
4+
display(true)
5+
display(Display.Box("hello"))
6+
// If we don’t have a Display for the type inside the Box,
7+
// calls to display should fail to compile:
8+
9+
// display(Box(123))
10+
11+
val ca = encode(123.4)
12+
val cb = decode[Double]("123.4")
13+
14+
val cc = encode(Codec.Box(123.4))
15+
val cd = decode[Codec.Box[Double]]("123.4")
16+
17+
// 3.5.2 Functor Syntax
18+
val func1 = (a: Int) => a + 1
19+
val func2 = (a: Int) => a * 2
20+
val func3 = (a: Int) => s"${a}!"
21+
val func4 = func1.map(func2).map(func3)
22+
23+
func4(123)
24+
25+
def doMath[F[_]](start: F[Int])(using functor: Functor[F]): F[Int] =
26+
start.map(n => n + 1 * 2)
27+
28+
doMath(Option(20))
29+
doMath(List(1, 2, 3))
30+
31+
// 8.7.1 Contravariant in Cats
32+
import cats.Contravariant
33+
import cats.Show
34+
import cats.instances.string.catsStdShowForString
35+
36+
val showString = Show[String]
37+
38+
// trait Contravariant[F[_]]:
39+
// def contramap[A, B](fa: F[A])(f: B => A): F[B]
40+
41+
val showSymbol: Show[Symbol] = Contravariant[Show].contramap(showString)((sym: Symbol) => s"'${sym.name}")
42+
43+
showSymbol.show(Symbol("dave"))
44+
45+
import cats.syntax.contravariant.toContravariantOps
46+
47+
showString
48+
.contramap[Symbol](sym => s"'${sym.name}")
49+
.show(Symbol("dave"))
50+
51+
// 8.7.2 Invariant in Cats
52+
import cats.Monoid
53+
import cats.instances.string.catsKernelStdMonoidForString
54+
import cats.syntax.invariant.toInvariantOps
55+
import cats.syntax.semigroup.catsSyntaxSemigroup
56+
57+
given Monoid[Symbol] =
58+
Monoid[String].imap(Symbol.apply)(_.name)
59+
60+
Monoid[Symbol].empty
61+
62+
Symbol("a") |+| Symbol("few") |+| Symbol("words")
63+
64+
// 8.8 Aside: Partial Unification
65+
val f1 = (x: Int) => x.toDouble
66+
val f2 = (y: Double) => y * 2
67+
68+
val f3 = func1.map(func2)
69+
70+
val either: Either[String, Int] = Right(123)
71+
72+
either.map(_ + 1)
73+
74+
val f3a: Int => Double =
75+
a => f2(f1(a))
76+
77+
val f3b: Int => Double =
78+
f2.compose(f1)
79+
80+
// error: value contramap is not a member of Double => Double
81+
// val f3c = f2.contramap(f1)
82+
83+
type <=[B, A] = A => B
84+
85+
type F[A] = Double <= A
86+
87+
val f2b: Double <= Double = f2
88+
89+
val f3c = f2b.contramap(f1)

ch08/test/src/TreeSpec.scala

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package ch08
2+
import org.scalatest.funspec.AnyFunSpec
3+
import org.scalatest.matchers.should.Matchers.shouldBe
4+
import cats.syntax.functor.toFunctorOps
5+
6+
class TreeSpec extends AnyFunSpec:
7+
describe("Tree Functor"):
8+
it("should map on leaf"):
9+
val actual = Tree.leaf(100).map(_ * 2)
10+
actual `shouldBe` Leaf(200)
11+
12+
it("should map on branch"):
13+
val actual = Tree.branch(Tree.leaf(10), Tree.leaf(20)).map(_ * 2)
14+
actual `shouldBe` Branch(Leaf(20), Leaf(40))

0 commit comments

Comments
 (0)