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
- 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
- What is an interface syntax in cats?
- What do Show and Eq do in cats?
- Members of show can be presented as strings.
- Explain covariance, contravariance and invariance.
- Covariance
- Contravariance
- Invariance F[A] and F[B] are never subtypes of each other, regardless of the relationship between A and B.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
Chapter 2: Monoids and Semigroups: Answers
- Define monoid and semigroup and give examples of each.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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
- 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).
- What is a covariant functor?
- 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.
- What is a contravariant functor?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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
- What is a monad?
- 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.
- 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.
- What does each line print?
- 'Now' is eager and memoized (val). 'Later' is lazy and memoized (lazy val). 'Always' is lazy and not memoized (def).
- What is the output of the following code snippet and why?
- "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.
- 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.
- What is the reader monad for?
- Reader[A,B] represents a function A => B. It allows sequencing of operations that depend on some input.
- 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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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))) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
Chapter 5: Monad Transformers
- What are monad transformers used for?
- These are for composing monads, eg for giving us nice ways of handling Future[Option[T]]