Cats

Why? https://typelevel.org/cats/jump_start_guide.html

And learning resources: https://typelevel.org/cats/resources_for_learners.html

If you convert every type into a higher kinded type, then you can use a common set of functions for every situation. A higher kinded type is a type which has a type parameter, for instance List[Int], or Option(String). If we make a new higher kinded type called Monad, and provide implicits which can convert from any type into a Monad, and in the monad provide useful stuff like Map, Id, and so on, then we can write libraries which will work for all data types, with a lot less leg work. Scala context parameter let you pull in the implicits with very little code.

ie, Given implicits, context paramters and type classes you can create your own ‘language’ within Scala. After about 5 days of study, you can use F[ _ ] syntax in your code combined with some imports and this will allow you to use map, flatmap with Monads or andThen with SemiGroupal. Which is not in itself worth the effort.

However, people have been building nice libraries on top of cats, so it is now important to know how it works. These libraries can be clearer (maybe) than the akka world that preceded them. In particular, side effects can be moved to ‘the edge’ using cats.IO.

Note tha Akka is also a big learning curve - from Spray, Play, Akka.Http, Akka streams, Lagom etc. So, if you are starting you could ignore that stack and learn this one instead. Sadly I’ve now done both.

cats.io

Which is where you end up when you want pure functions encapsulated as steps in a stream.

fs2, http4s and doobie

Is where you rapidly find yourself in June 2020, and these are actually the place I started which meant I had to spent 5 days learning cats. And then another 5 days a month later when I had some practical experience.

Hacking with http4s, doobie and cats-effects

Recap

A pure function has no side effects. The outputs of the function are only dependent on the inputs.

trait F[+A] // Covarience: type F[B] is a subtype of F[A] if B is a suptype of A  
trait F[-A] // Contravarience: type F[B] is a subtype of F[A] is A is a suptype of B
val ~> =1  // Tilde greater than is just a name.  Like  n, or T, or fooBar
def foo[T : B : C] ={ // Context bound.  Syntactic sugar for an implicit param B[T], C[T], ie there will exist a B[T] and C[T] in implicit scope.

//Higher Kinded types, ie  F[_] means that F may not be a simple type, like Int or String, but instead
//a one-argument type constructor, like List or Option.
// A 'type constructor' with 1 hole = a hole where you supply the type, declared using F[_]
// Its all tied into Higher Kinded types.  Read about it.

def foobar[ F[_] ] = { // Declare F using underscores, google for "Higher Kinded" types
  val functor = Functor.apply[F]  // Reference F without underscores
}
List[A] // a type, produced using a type parameter
self : User => // You can place this in a trait to insist that you have to mix in the type of self
        // when you create an instance of the trait.
self => // an alias for this used when there is an inner class/trait to make outer scope visible.
for () yield // in scala is sugar for composition of multiple monadic operations - see link below

associativity: (1+2) + 3 == 1+(2+3)

For comprehensions

Quote: for-expression is used to create new collections from existing collections.

For comprehensions need to be reviewed. for comprehension in the language spec.

Stolen from the above page, into simple reminder form

for(x <- c1; y <- c2; z <-c3) {...} // equivalent to
c1.foreach(x => c2.foreach(y => c3.foreach(z => {...})))

for(x <- c1; y <- c2; z <- c3) yield {...}  // equivalent to
c1.flatMap(x => c2.flatMap(y => c3.map(z => {...})))

for(x <- c; if cond) yield {...} // equivalent to
c.filter(x => cond).map(x => {...})

Scala lang docs. For comprehensions can be used for any datatype that supports withFilter, map and flatMap.

Cripsheet

https://www.scalawithcats.com/dist/scala-with-cats.pdf

All content is taken from their book above, which they are about to rewrite…

Do skim this, but in reality, knowing Monad is often enough.

.show Rather than .toString. Helper exists Show.show to create one for your class.

import cats.syntax.eq._
=== Compares two objects and errors if different types.  Same as eqInt.eqv(123, 123)
=!= compares for inequality

Semigroup

Supports an associative combine operation.

trait Semigroup[A] {
  def combine(x: A, y: A): A  // Must be associative
}

Monoid

Can take two inputs of the same type and return an output of this type, also has an empty elememt

trait Monoid[A] entends Semigroup {
  def empty: A // Can be the identity element,
               // ie then def in above as x, the output is y, or fed in as y, the output is x.
}

Functor, map, can be sequenced

Provide a map which converts a single input param of Type A to Type B. It can also lift that function of type A => B to a function that operates over a functor and has type F[A] => F[B]. Note that functions in Scala already have andThen, which in cats is replaced with map.

val list1 = List(1, 2, 3)
// list1: List[Int] = List(1, 2, 3)
val list2 = Functor[List].map(list1)(_ * 2)
val func = (x: Int) => x + 1
// func: Int => Int = <function1>
val liftedFunc = Functor[Option].lift(func)
// liftedFunc: Option[Int] => Option[Int] = cats.Functor$$Lambda$5433
liftedFunc(Option(1))
// res1: Option[Int] = Some(2)

Why? Well, maybe you are writing some logic which can work for anything. Because every operation can be converted to a Functor then you could write your logic for any type.

Contravarient Functor, contramap, can be prepended

F[B] contramap A => B gives F[A]. So, contramap belonging to a thing of type B, takes a function that converts type A to type B as a parameter. It then returns a thing of type A. Contramap is an operation on the Contravarient Functor, representing “prepending” an operation to a chain.

Invarient Functor, imap, is bidirectional

F[A] given two function params A => B , B => A produces F[B]

We already have Covarient and Contravarient Functors. So imap is on Invarient Functors. imap is kinda like a combination of map and contramap.

Great little description in Scala with Cats at the end of section 3.6.

Monad, flatMap, pure

A monad is a mechanism for sequencing computations. It implements flatMap, and every Monad is also a Functor.

flatMap, of type (F[A], A => F[B]) => F[B] - actually in type class FlapMap

pure, of type A => F[A] - actually in type class Applicative

Applicative and FlatMap type classes are extended by Monad. Monad also provides the methods from Functor.

Basically learn Monad, then just use Http4s, Rho and Doobie.

Cats standards

All the type classes have an apply method which is typed, so you can do:

Monoid[String].combine("Hi ", "there") // same as Monoid.apply[String].combine("Hi ", "there")

To access the apply methods on SemiGroup, Monoid, Functor you import cats.instances

import cats.Functor
import cats.instances.list._
import cats.instances.option._

Cats Operators

val s = "Hi " |+| "there"   // |+| is the semigroup.combinme
success1 *> success2 // useful for delayed computation to preserve errors
   // *> alias for productR, and <* is an alias for productL
thingA >> thingB // from FlatMapSyntax, 2nd operant invoked call by name.

Reminder

import cats.Monad
import cats.syntax.functor._ // for map
import cats.syntax.flatMap._ // for flatMap

def sumSquare[F[_]: Monad](a: F[Int], b: F[Int]): F[Int] =
  a.flatMap(x => b.map(y => x*x + y*y))

So 1.

F[ _ ] : Monad

is the context bound syntax, which is sugar for saying there is an implicit in scope to create Monad[ F [ _ ]], ie whatever the type of _ turns out to be there will be an implicit to create a Monad from it.

2.

a: F[Int]

so variable a is a thing typed to Int. So we also know from above (1) that there is an implicit which can create a Monad from F[Int], which means we know we can invoke map and flatMap and pure and so on because those operations will exist because they are provided by the Monad type class.

b: F[Int]

again we know we can create a Monad for b. Returning F[Int], well, ok, thats the return type.

All this means we can implement it as:

a.flatMap(x => b.map(y => x*x + y*y))

But, since map and flatMap are better expressed by For Comprehensions, which are clearer then:

def sumSquare[F[_]: Monad](a: F[Int], b: F[Int]): F[Int] =
  for {
    x <- a
    y <- b
  } yield x*x + y*y

Id

Simple values are not typed, so you can use Id to create a type class for simple values and so call your sumSquare above, not only that but Cats provides instances of Functor and Monad for Id, so…

val a = Monad[Id].pure(3)

Misc Monads

MonadError

type ErrorOr[A] = Either[String, A]
val monadError = MonadError[ErrorOr, String]
val success = monadError.pure(42)
val failure = monadError.raiseError("badness")
monadError.handleError(failure) {
  case "badness" => monadError.pure("It's OK")
  case _ => monadError.raiseError("It's not OK")
}

Eval, Writer, Reader, Either, and many more

Read about it when you see it. Its very dull.

Transformers

Monads don’t compose well, unless you implement Transformers for all the common ones. So they did and called them OptionT, and so on. ie a T letter at the end.

Semigroupal

Two objects which are independent can be combined. Map implies ordering, and will fail with the first failure, perhaps you want to perform an operation on all values and capture all errors. But product on Monad is fail fast, so they have Validated which is a Semigroupal but not a Monad, so implements product to accumulate errors…. ie learn the cats library and move on.

trait Semigroupal[F[_]] {
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
}

fa and fb are independent, can be executed in any order. There is apply syntax to combine up to 22 values into Tuples, as well as mapN for any arity.

Applicative

Take a look at the API for ApplicativeError (which is shared by MonadError). Its how you handle streaming errors. In particular: .recover, .recoverWith, .handleError, .handleErrorWith, .valueOr

Foldable

A Monad with apply which encapsulated FoldLeft and FoldRight and other functions. Has combineAll and foldMap to combine all the elements in the sequence.

Provides a stack safe version of foldRight.

Traverse

Standard Scala has Future.traverse.