Coding with Cats

Advent of Code

https://github.com/lsug/advent-of-code-typelevel

Exercise

git checkout day-1-step-0

sbt compile test

# go to advent/solutions/Day1.scala and fix the tests
Code a functional solution to Part 1

Solutions

git diff day-1-step-0 day-1-step-1

def fuel(mass: Int): Int =
   (mass / 3) - 2


def calculateFuels(masses: List[Int]): List[Int] =
  masses.map(fuel)


def sumFuels(fuels: List[Boolean]): Int =
  fuels.sum


def sumOfFuel(masses: List[Boolean]): Int =
  sumFuels(calculateFuels(masses))

Abstraction

def weLikeCats: Boolean = ???
def weLikeCats(b: Boolean): Boolean = ???
def weLikeCats[A](a: A): A = ???
def weLikeCats(b: Boolean): Boolean = ???

Exercise

git checkout day-1-step-1-abstraction

sbt compile test

# go to advent/solutions/Abstraction.scala and fix the tests
Complete the Abstraction exercises
  def fold[A, B](o: Option[A], f: A => B, b: B): B = {
    o match {
      case None    => b
      case Some(v) => f(v)
    }
  }
  
def list[A]: List[A] = Nil
def fuel[A](mass: A): A = {
  (mass / 3) - 2
}
def fuel[A](mass: A)
  (quotient: (A, A) => A, 
   minus: (A, A) => A,
   two: A,
   three: A): A = {
  minus(quotient(mass, three), two)
}
fuel(12)(_ / _, _ - _, 2, 3)
trait Mass[A] {
  def quotient(a: A, b: A): A
  def minus(a: A, b: A): A
  def two: A
  def three: A
}
def fuel[A](mass: A)(M: Mass[A]): A = 
  M.minus(M.quotient(mass, M.three), M.two)
val intHasMass: Mass[Int] = new Mass[Int] {
  def quotient(a: Int, b: Int): Int = a / b
  def minus(a: Int, b: Int): Int = a - b
  def two: Int = 2
  def three: Int = 3
}
fuel[Int](12)(intHasMass)
def fuel[A](mass: A)(implicit M: Mass[A]): A
implicit val intHasMass: Mass[Int] = 
  new Mass[Int] { 
    // code
  }
fuel(12)

Exercise

# peek at the solution if you need to
git diff day-1-step-1 day-1-step-2
Create a Mass typeclass and use it in the fuel function

Cats

Semigroup

((a combine b) combine c) should be (a combine (b combine c))
trait Semigroup[A] {
   def combine(a: A, b: A): A
}

Monoid

(a combine empty) should be(empty combine a)

(a combine empty) should be(a)
trait Monoid[A] extends Semigroup[A] {
   def empty: A
}
import cats._
import cats.implicits._
def sumFuels[A](fuels: List[A])
               (implicit M: Monoid[A]): A = ???

Exercise

git checkout day-1-step-2
Use a monoid for sumFuels and sumOfFuel

Solution

git diff day-1-step-2 day-1-step-3

Exercise

git checkout day-1-step-3

sbt compile test

# go to advent/solutions/Day1.scala and fix the tests
Code a functional solution to Part 2

Solution

git diff day-1-step-3 day-1-step-4

Order

trait Order[A] {
   def eqv(a: A, b: A): Boolean
   def compare(a: A, b: A): Int
}
a < b

Syntax

Exercise

git checkout day-1-step-4
Use Monoid and Order in Part 2

Solution

git diff day-1-step-4 day-1-step-5

Type constructors

Functor

fa.map(f).map(g) should be fa.map(f.andThen(g))
trait Functor[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
}
fa.map(x => x) = fa

Foldable

trait Foldable[F[_]] {
  def foldLeft[A, B](fa: F[A], b: B)(f: (B, A) => B): B
}

Exercise

git checkout day-1-step-5
Use Functor and Foldable in Part 1

Solution

git diff day-1-step-5 day-1-step-6

More fun with cats

Coding with Cats

By Zainab Ali

Coding with Cats

  • 1,500