cats¶
- ROT:
import cats._; import cats.data._; import cats.implicits - All of the following constructs are same:
- Monad Transformers use Classes to combine effects, whereas MTL defines Type classes to add behavior to Monad Transformers
Validated¶
- prefer not using
Validateddirectly, but instead indirectly viaParallel Validated.condNec(c, a, e): ValidatedNec[E, A]: create a validated based on conditionValidated.fromOption(o, e): Validated[E, A]: create validated from an option.toValidatedNec: convertValidated[E, A]toValidatedNec[E, A]
map: transform valid valuesvalidateBoth: aggregate valid dataflatMap: chain validation rules, fail-fast by reporting the first failurevalidateEach(traverse): validate all values and return either accumulated error messages or valid values- returns, for example,
Validated[Seq[A]]instead ofSeq[Validated[A]]
- returns, for example,
.parMapN: convertsEithers toValidated, combines withmapNand converts back the result toEither
Show¶
- use java style:
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)
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
- to obtain the next state's value, use
runmethod, whic h returns anEval, 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 stateflatMap: threads the state to the next and passes the return value as theflatMapargumentx.flatMap(_ => y)===x >> y
Eval¶
- useful functions:
Monad Transformers¶
- allow composing monads:
ReaderT[Option, AccountRepo, A]===Reader[AccountRepo, Option[A]], combines two monads that a function needs:Option[A]andReader[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
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
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