Sunday, 26 April 2020

Scala with Cats: Answers to revision questions

I'm studying the 'Scala with Cats' book. I want the information to stick so I am applying a technique from 'Ultralearning' where I make revision notes in the questions which I regularly review. This post contains the questions and their answers.

Chapter 1: Introduction: Answers

  1. What is a type class? 
    • It’s a way of defining some behaviour that many types can adhere to. In scala it's a trait with a parameter
  2. What is an interface syntax in cats?
  3. What do Show and Eq do in cats? 
    • Members of show can be presented as strings. 
    • object Main extends App {
      import cats._
      import cats.implicits._
      println(123.show)
      println("xyc".show)
      println(true.show)
      // using show on custom objects
      case class Cat(name: String)
      val cat = Cat("Ruby")
      implicit val catShow: Show[Cat] = Show.show[Cat](c => "Cat called " + c.name)
      println(cat.show)
      }
  4. Explain covariance, contravariance and invariance. 
    • Covariance
    • object Main extends App {
      /*
      Covariant: F[B] is a subtype of F[A] if B is a subtype of A
      */
      trait Shape {}
      case class Circle(radius: Double) extends Shape
      /*
      if we change this to [A] or [-A] we cannot pass
      covariantCircle into func
      */
      trait Covariant[+A] {
      def print(): Unit
      }
      val covariantCircle: Covariant[Circle] = () => println(s"Circle")
      val covariantShape: Covariant[Shape] = () => println(s"Shape")
      def func(c: Covariant[Shape]): Unit = c.print()
      func(covariantCircle)
      func(covariantShape)
      }
      view raw Covariant.scala hosted with ❤ by GitHub
    • Contravariance
    • object Main extends App {
      /*
      Contravariant: F[B] is a subtype of F[A] if A is a subtype of B
      */
      trait Shape {}
      case class Circle(radius: Double) extends Shape
      /*
      if we change this to [A] or [+A] we cannot pass
      covariantShape into func
      */
      trait Contravariant[-A] {
      def print(): Unit
      }
      val covariantCircle: Contravariant[Circle] = () => println(s"Circle")
      val covariantShape: Contravariant[Shape] = () => println(s"Shape")
      def func(c: Contravariant[Circle]): Unit = c.print()
      func(covariantCircle)
      func(covariantShape)
      }
    • Invariance
    • F[A] and F[B] are never subtypes of each other, regardless of the relationship between A and B.

Chapter 2: Monoids and Semigroups: Answers

  1. Define monoid and semigroup and give examples of each. 
  2. // eg union of non empty sets
    trait MySemigroup[A] {
    // must be associative
    def combine(a1: A, a2: A): A
    }
    // eg union of sets, where the empty set is the identity element
    trait MyMonoid[A] extends MySemigroup[A] {
    // is an identity element
    def empty: A
    }

Chapter 3: Functors: Answers

  1. What is a type constructor? 
    • A type constructor is like a type but it has a 'hole to fill'. Eg List is a type constructor because it has one 'hole' to fill. Note the difference between List (a type constructor) and List[A] (a type, using a type parameter) and List[Int] (a concrete type).
  2. What  is a  covariant functor? 
  3. /*
    Functor Laws:
    1. Identity
    fa.map(a => a) == fa
    2. Composition
    fa.map(f).map(g) = fa.map(g(f(_)))
    */
    trait MyFunctor[F[_]] {
    def map[A, B](fa: F[A], f: A => B): F[B]
    }
  4. Write a method 'power' which can operate on either List(1,2,3) or Option(2) and returns List(1,4,9) and Option(4) respectively. 
  5. object Main extends App {
    import cats._
    import cats.implicits._
    def power[F[_]](f: F[Int])(implicit func: Functor[F]): F[Int] = {
    f.map(x => x*x)
    }
    println(power(List(1,2,3)))
    println(power(Option(2)))
    // alternative syntax
    def power2[F[_] : Functor](in: F[Int]): F[Int] = {
    in.map(i => i * i)
    }
    }
  6. What is a contravariant functor? 
  7. // has a contramap method
    trait MyContravariantFunctor[F[_]] {
    def contramap[A, B](fa: F[A], f: B => A): F[B]
    }
    // We can use contramap to build a show for Salary from the show for Money
    import cats._
    import cats.implicits._
    case class Money(amount: Int)
    case class Salary(size: Money)
    implicit val showMoney: Show[Money] = Show.show(m => s"$$${m.amount}")
    implicit val showSalary: Show[Salary] = showMoney.contramap(_.size)
    println(Money(5).show)
    println(Salary(Money(50)).show)

Chapter 4: Monads

  1. What is a monad? 
  2. /*
    3 Monad laws
    1. Right identity: pure(a).flatMap(func) == func(a)
    2. m.flatMap(pure) = m
    3. m.flatMap(f).flatMap(g) = m.flatMap(x => f(x).flatMap(g))
    */
    trait MyMonad[F[_]] {
    def pure[A](a: A): F[A]
    def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
    // all monads are functors because we can define map in terms of pure and flatMap
    def map[A, B](fa: F[A], f: A => B): F[B] = flatMap(fa)(a => pure(f(a)))
    }
  3. What is the 'Id' monad for? 
    • Imagine you have a method which operates on a Monad of Ints, eg power method so for Option(2) it returns Option(4) and for List(1,2) it returns List(1,4). To use this method on regular ints we need to wrap our plain Ints in a monad, hence Id. 
  4. What is the 'Eval' monad for? 
    • This is for abstracting over different modes of computation, eager or lazy. Memoized computations run once then are cached, and can run lazily or eagerly. vals are eager and memoized. Defs are lazy and non memoized. Lazy vals are lazy and memoized. Eval has three subtypes, Now, Later and Always. 
    • It also has 'defer' which can be used to avoid stack overflow in recursive methods.
  5. What does each line print? 
  6. object Main extends App {
    import cats._
    import cats.implicits._
    private val now: Eval[Double] = Eval.now(math.random)
    println(now.value)
    println(now.value)
    private val later: Eval[Double] = Eval.later(math.random)
    println(later.value)
    println(later.value)
    private val always: Eval[Double] = Eval.always(math.random)
    println(always.value)
    println(always.value)
    }
    • 'Now' is eager and memoized (val). 'Later' is lazy and memoized (lazy val). 'Always' is lazy and not memoized (def). 
  7. What is the output of the following code snippet and why? 
  8. object Main extends App {
    import cats._
    import cats.implicits._
    val ans: Eval[Int] = for {
    a <- Eval.now { println("a"); 40 }
    b <- Eval.always { println( "b" ); 2 }
    c <- Eval.later { println( "c" ); 2 }
    } yield {
    println("Adding!")
    a + b + c
    }
    println("---")
    ans.value // ignored
    ans.value // ignored
    }
    • "Now" is calculated eagerly so is printed first. However mapping functions are always called like defs so the output is 'a, ---, b, c, Adding!, b, c, Adding!' There is a memoize function that can be used when chaining. 
  9. What is the writer monad for?
    • This is for carrying a log along with some computation. It's particularly useful in multithreaded computation where normal log messages might get interleaved. Writer[W,A] has a log of type W and a result of type A.
  10. What is the reader monad for? 
    • Reader[A,B] represents a function A => B. It allows sequencing of operations that depend on some input. 
  11. What is the state monad for? 
    • The state monad allows us to pass additional state around as part of a computation. We can use it to model mutable state in a purely functional way, without the mutation. State[S, A] represents functions of type S => (S, A). S is the type of the state and A is the result type. 

Chapter 5: Monad Transformers

  1. What are monad transformers used for? 
    • These are for composing monads, eg for giving us nice ways of handling Future[Option[T]]


1 comment:

Scala with Cats: Answers to revision questions

I'm studying the 'Scala with Cats' book. I want the information to stick so I am applying a technique from 'Ultralearning...