Skip to content

Commit

Permalink
Complete ch08
Browse files Browse the repository at this point in the history
  • Loading branch information
Abhijit Sarkar committed Dec 22, 2024
1 parent 045f097 commit 5b6d30d
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ assumeStandardLibraryStripMargin = true
project.excludePaths = [
"glob:**/ch04/src/**.scala",
"glob:**/ch06/src/Cat.scala",
"glob:**/ch07/src/*.scala"
"glob:**/ch07/src/*.scala",
"glob:**/ch08/src/*.scala"
]
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The older code is available in branches.
5. [Reified Interpreters](ch05)
6. [Using Cats](ch06)
7. [Monoids and Semigroups](ch07)
8. [Functors](ch08)

## Running tests
```
Expand Down
41 changes: 41 additions & 0 deletions ch08/src/Codec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ch08

/*
8.6.2.1 Transformative Thinking with imap
Implement the imap method for Codec.
*/
trait Codec[A]:
def encode(value: A): String
def decode(value: String): A
def imap[B](dec: A => B, enc: B => A): Codec[B] =
val self = this
new Codec[B]:
override def encode(value: B): String =
self.encode(enc(value))

override def decode(value: String): B =
dec(self.decode(value))

def encode[A : Codec as c](value: A): String =
c.encode(value)

def decode[A : Codec as c](value: String): A =
c.decode(value)

object Codec:
final case class Box[A](value: A)

given stringCodec: Codec[String]:
def encode(value: String): String = value
def decode(value: String): String = value

given Codec[Int] =
stringCodec.imap(_.toInt, _.toString)

// Demonstrate your imap method works by creating a Codec for Double.
given Codec[Double] =
stringCodec.imap(_.toDouble, _.toString)

// Implement a Codec for the Box type.
given boxCodec: [A : Codec as c] => Codec[Box[A]] =
c.imap[Box[A]](Box(_), _.value)
42 changes: 42 additions & 0 deletions ch08/src/Display.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package ch08

/*
8.6.1.1 Exercise: Showing off with Contramap
Implement the contramap method for Display above.
*/
trait Display[A]:
def display(value: A): String

def contramap[B](f: B => A): Display[B] =
val self = this
new Display[B]:
def display(value: B): String =
self.display(f(value))

def display[A: Display as d](value: A): String =
d.display(value)

object Display:
final case class Box[A](value: A)

given Display[String]:
def display(value: String): String =
s"'${value}'"

given Display[Boolean]:
def display(value: Boolean): String =
if (value) "yes" else "no"

/*
Define an instance of Display for the following Box case class.
Rather than writing out the complete definition from scratch
(new Display[Box] etc...), create your instance from an existing
instance using contramap.
*/
// Given a Display[A], and a Box[A], we can create
// Display[Box[A]] by the reverse mapping Box[A] => A.
// The forward mapping would be A => Box[A].

// Parameterized typeclass with named context bound.
given [A : Display as d] => Display[Box[A]] =
d.contramap[Box[A]](_.value)
28 changes: 28 additions & 0 deletions ch08/src/Tree.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package ch08

import cats.Functor

sealed trait Tree[+A]

final case class Branch[A](left: Tree[A], right: Tree[A]) extends Tree[A]

final case class Leaf[A](value: A) extends Tree[A]

object Tree:
/* 8.5.4 Exercise: Branching out with Functors
Write a Functor for the following binary tree data type.
Verify that the code works as expected on instances of Branch and Leaf.
*/
given Functor[Tree]:
override def map[A, B](fa: Tree[A])(f: A => B): Tree[B] =
fa match
case Branch(l, r) => Branch(map(l)(f), map(r)(f))
case Leaf(value) => Leaf(f(value))

// The compiler can find a Functor instance for Tree but not for Branch or Leaf (Functor is invariant in F).
// Let's add some smart constructors to compensate.
def branch[A](left: Tree[A], right: Tree[A]): Tree[A] =
Branch(left, right)

def leaf[A](value: A): Tree[A] =
Leaf(value)
89 changes: 89 additions & 0 deletions ch08/src/ch08.worksheet.sc
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import ch08.{display, Display, encode, decode, Codec}

display("hello")
display(true)
display(Display.Box("hello"))
// If we don’t have a Display for the type inside the Box,
// calls to display should fail to compile:

// display(Box(123))

val ca = encode(123.4)
val cb = decode[Double]("123.4")

val cc = encode(Codec.Box(123.4))
val cd = decode[Codec.Box[Double]]("123.4")

// 3.5.2 Functor Syntax
val func1 = (a: Int) => a + 1
val func2 = (a: Int) => a * 2
val func3 = (a: Int) => s"${a}!"
val func4 = func1.map(func2).map(func3)

func4(123)

def doMath[F[_]](start: F[Int])(using functor: Functor[F]): F[Int] =
start.map(n => n + 1 * 2)

doMath(Option(20))
doMath(List(1, 2, 3))

// 8.7.1 Contravariant in Cats
import cats.Contravariant
import cats.Show
import cats.instances.string.catsStdShowForString

val showString = Show[String]

// trait Contravariant[F[_]]:
// def contramap[A, B](fa: F[A])(f: B => A): F[B]

val showSymbol: Show[Symbol] = Contravariant[Show].contramap(showString)((sym: Symbol) => s"'${sym.name}")

showSymbol.show(Symbol("dave"))

import cats.syntax.contravariant.toContravariantOps

showString
.contramap[Symbol](sym => s"'${sym.name}")
.show(Symbol("dave"))

// 8.7.2 Invariant in Cats
import cats.Monoid
import cats.instances.string.catsKernelStdMonoidForString
import cats.syntax.invariant.toInvariantOps
import cats.syntax.semigroup.catsSyntaxSemigroup

given Monoid[Symbol] =
Monoid[String].imap(Symbol.apply)(_.name)

Monoid[Symbol].empty

Symbol("a") |+| Symbol("few") |+| Symbol("words")

// 8.8 Aside: Partial Unification
val f1 = (x: Int) => x.toDouble
val f2 = (y: Double) => y * 2

val f3 = func1.map(func2)

val either: Either[String, Int] = Right(123)

either.map(_ + 1)

val f3a: Int => Double =
a => f2(f1(a))

val f3b: Int => Double =
f2.compose(f1)

// error: value contramap is not a member of Double => Double
// val f3c = f2.contramap(f1)

type <=[B, A] = A => B

type F[A] = Double <= A

val f2b: Double <= Double = f2

val f3c = f2b.contramap(f1)
14 changes: 14 additions & 0 deletions ch08/test/src/TreeSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ch08
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers.shouldBe
import cats.syntax.functor.toFunctorOps

class TreeSpec extends AnyFunSpec:
describe("Tree Functor"):
it("should map on leaf"):
val actual = Tree.leaf(100).map(_ * 2)
actual `shouldBe` Leaf(200)

it("should map on branch"):
val actual = Tree.branch(Tree.leaf(10), Tree.leaf(20)).map(_ * 2)
actual `shouldBe` Branch(Leaf(20), Leaf(40))

0 comments on commit 5b6d30d

Please sign in to comment.