Skip to content

cats

  • ROT: import cats._; import cats.data._; import cats.implicits
  • All of the following constructs are same:
    implicitly[Applicative[Option]].pure(1)
    Applicative[Option].pure(1)
    1.pure[Option]
    
  • Monad Transformers use Classes to combine effects, whereas MTL defines Type classes to add behavior to Monad Transformers

Validated

  • prefer not using Validated directly, but instead indirectly via Parallel
  • Validated.condNec(c, a, e): ValidatedNec[E, A]: create a validated based on condition
  • Validated.fromOption(o, e): Validated[E, A]: create validated from an option
    • .toValidatedNec: convert Validated[E, A] to ValidatedNec[E, A]
  • map: transform valid values
  • validateBoth: aggregate valid data
  • flatMap: chain validation rules, fail-fast by reporting the first failure
  • validateEach (traverse): validate all values and return either accumulated error messages or valid values
    • returns, for example, Validated[Seq[A]] instead of Seq[Validated[A]]
  • .parMapN: converts Eithers to Validated, combines with mapN and converts back the result to Either

Show

  • use java style:
    implicit val aShow: Show[A] = Show.fromToString
    

Applicative

Implement ap in terms of map2

def map2(va: Validated[A], Validated[B])(f: (A, B) => C): Validated[C] = ???
def ap(vf: Validated[A => B])(va: Validated[A]): Validated[B] = map2(vf, va)((f, a) => f(a))

Implement map2 in terms of ap

def ap(vf: Validated[A => B])(va: Validated[A]): Validated[B] = ???
def map2(va: Validated[A], Validated[B])(f: (A, B) => C): Validated[C] =
val g: A => B => C = f.curried // = A => B => f(a, b)
val fbc = ap(pure(g))(va)
ap(fbc)(vb)
// ap(ap(pure(f.curried))(va))(vb)
- Tip: use traverse_ (instead of tranverse) to ignore the result and produce Unit - >> is lazy v/s *> is strict

State

  • conceptually, it's a function that takes an initial state and returns an updated state and a value
    class State[S, A](val run: S => (S, A))
    
  • to obtain the next state's value, use run method, whic h returns an Eval, and then access the value: state.run(s).value
  • useful methods:
    def get[S]: State[S, S] = State(s => (s, s))  // return current state
    def set[S](s: S): State[S, Unit] = State(_ => (s, ()))   // set or overwrite state
    def modify[S](f: S => S): State[S, Unit] = State(s => (f(s), ()))   // update state using a function
    def inspect[S](f: S => T): State[S, T] = State(s => (s, f(s)))  // apply a function to state without modifying it
    
  • map: maps the return value from state without affecting the state
  • flatMap: threads the state to the next and passes the return value as the flatMap argument
    • x.flatMap(_ => y) === x >> y
      val t1 = State.modify[Int](_ * 2)
      val t2 = State.modify[Int](_ * 3)
      (t1 >> t1).run(10).value // (60, ())
      

Eval

  • useful functions:
    def now[A](a: A): Eval[A] = ???        // eager, no memo === val
    def later[A](a: => A): Eval[A] = ???   // lazy, memo === lazy val
    def always[A](a: => A): Eval[A] = ???  // lazy, no memo === def
    def defer[A](a: => Eval[A]) = ???      // defers the computation of Eval
    

Monad Transformers

  • allow composing monads: ReaderT[Option, AccountRepo, A] === Reader[AccountRepo, Option[A]], combines two monads that a function needs: Option[A] and Reader[AccountRepo, A]
  • They are built inside-out. That is, any T monad-transformer represents the inner monad of the stack. E.g. OptionT[F[_], A] === F[Option[A]]
  • Nested monad transformers
    trait AccountRepo
    type ErrorOr[A] = Either[String, A]
    type ErrorOrOpt[A] = OptionT[ErrorOr, A] // === Either[String, Option[A]]
    type AccountOp[A] = ReaderT[ErrorOr, AccountRepo, A]
    

ReaderT

  • useful methods:
    trait AccountRepo
    type ErrorOr[A] = Either[String, A]
    type AccountOp[A] = ReaderT[ErrorOr, AccountRepo, A]
    val dummyRepo: AccountRepo = new AccountRepo()
    
    ReaderT.liftF[ErrorOr, AccountRepo, Int](5.asRight[String]) // lift F[A] -> ReaderT[F, R, A]
    ReaderT((_: AccountRepo) => 5.asRight[String]) // construct from F[A]
    5.pure[AccountOp]  // A -> ReaderT[F, R, A]
    // ReaderT[ErrorOr, R, A] => ReaderT[Option, R, A]
    5.pure[AccountOp].mapF {
        case Right(a) => Some(a)
        case Left(e) => None
    }
    
  • monadic methods
    5.pure[AccountOp].map(_ + 1) // === 6.pure[AccountOp]
    5.pure[AccountOp].flatMap(n => (n+1).pure[AccountOp]) // === 6.pure[AccountOp]
    

OptionT

type ErrorOrOpt = OptionT[ErrorOr, A] // === ErrorOr[Option[A]] == Either[String, Option[A]]

// useful methods:
5.pure[ErrorOrOpt] // === OptionT(Option(5).asRight[String])
5.pure[ErrorOrOpt].flatMap(n => (n+1).pure[ErrorOrOpt])
5.pure[ErrorOrOpt].subflatMap(n => Option(n+1))  // works only on inner monad (Option)
5.pure[ErrorOrOpt].semiflatMap(n => Right(n+1))  // works on outer monad
5.pure[ErrorOrOpt].flatMapF(n => Right(Some(n+1)))  // similar to flatMap and lift combined

Error Handling

flowchart LR
    A{Error type} --> B{Domain Error}
    B  --> B1[Accumulate]
    B1 --> B11[[Validated]]
    B  --> B2[Fail Fast]
    B2 --> B21[[Either/ADT]]
    A  --> C{Technical Error}
    C  --> C1[Recoverable]
    C1 --> C11[[ IO, raiseError, handleErrorWith ]]
    C  --> C2[Fatal]
    C2 --> C21[[ Fail ]]
- .handleError(f: (E) => A) converts an exception into success of the same type

Monadic Type Hierarchy

classDiagram
    Semigroup <|-- Monoid
    Functor <|-- Apply
    Apply <|-- Applicative
    class Semigroup{
        combine()
    }
    class Monoid{
        empty()
    }
    class Functor {
        map()
    }
    class Apply {
        ap()
    }
    class Applicative {
        pure()
    }
- Semigroup: Associative binary operation combine(A, A) => A, A forms Semigroup under combine - Monoid: A Semigroup plus an identity element combine(a, id) == a - Very useful for any reducing operation in parallel - Functor: Any container that can be mapped over - they must obey following laws - Identity: when mapped over by identity function, the result matches the original container - Composition: A functor mapped over by composition of two functions is same as functor mapped over by individual functions - Combine N things: 1. All same types?: - N > 1 Always?: Semigroup - Else: Monoid 1. Types are different, obtained independently?: Applicative/Apply 1. Else: Use Monad - Apply and Bind are Applicative and Monad without point (pure) - Free Functor Hierarchy - Free Functors: describe programs that change value, ie given A return B - Free Applicatives: describe programs that build data, eg DSLs, parsers, codecs - Free Monads: describe programs that build programs - State monad: tutorial to replace foldLeft with traverse