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]] .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)
- Tip: use
traverse_(instead oftranverse) to ignore the result and produceUnit >>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
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]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
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 operationcombine(A, A) => A, A formsSemigroupunder combineMonoid: ASemigroupplus an identity elementcombine(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:
-
All same types?:
-
N > 1 Always?:
Semigroup -
Else:
Monoid -
Types are different, obtained independently?:
Applicative/Apply -
Else: Use
Monad -
ApplyandBindareApplicativeandMonadwithoutpoint(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
Statemonad: tutorial to replacefoldLeftwithtraverse