Advanced
Resources
- Functional Programming in Scala (2014) by Paul Chiusano and Runar Bjarnason (Book)
- Functional Programming, Simplified (2017) by Alvin Alexander (Book)
- TODO "FP explained to ..." by Marc-Daniel Ortega (Book)(Unpublished)
- Constraints Liberate, Liberties Constrain by Runar Bjarnason (Video)
- Scalaz Presentation by Nick Partridge (Video)
- TODO Functional Structures in Scala by Michael Pilquist (Video)
- Functional Programming with Effects by Rob Norris (Video)
- Ladder of Functional Programming
Type Classes
Show
[ Show | ShowSpec | cats.ShowSpec ]
Show
provides textual representation
trait Show[T] {
def show(value: T): String
}
Eq
[ cats.EqSpec ]
Eq
provides equality between two instances of the same type
trait Eq[A] {
def eqv(x: A, y: A): Boolean
def neqv(x: A, y: A): Boolean = !eqv(x, y)
}
Semigroup
[ Semigroup | SemigroupSpec | SemigroupLaws | SemigroupLawsProp ]
Semigroup
provides combine which must be associative
trait Semigroup[A] {
def combine(x: A, y: A): A
}
Monoid
[ Monoid | MonoidSpec | MonoidLaws | MonoidLawsProp | cats.MonoidSpec ]
Monoid
is a Semigroup
with empty which must be an identity element
trait Monoid[A] extends Semigroup[A] {
def empty: A
}
Functor
[ Functor | FunctorSpec | FunctorLaws | FunctorLawsProp | cats.FunctorSpec ]
Covariant Functor
provides map which encapsulates sequencing computations
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
The Contravariant
functor provides an operation called contramap that represents prepending an operation to a chain
trait Contravariant[F[_]] {
def contramap[A, B](fa: F[A])(f: B => A): F[B]
}
The Invariant
functors provides an operation called imap that is informally equivalent to a combination of map
and contramap
trait Invariant[F[_]] {
def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B]
}
Semigroupal
Semigroupal
allows to combine contexts (Semigroup
allows to combine values)
trait Semigroupal[F[_]] {
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
}
Parameters fa
and fb
are independent effectful values: they can be computed in either order before passing them to product, in contrast to flatMap
which imposes a strict order on its parameters
Apply
Apply
allows to apply a parameter to a function within a context
trait Apply[F[_]] extends Semigroupal[F] with Functor[F] {
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
}
Applicative Functor
TODO laws
[ Applicative | ApplicativeSpec ]
Applicative
allows to lift a value into a context
trait Applicative[F[_]] extends Apply[F] {
def pure[A](a: A): F[A]
}
Monad
[ Monad | MonadSpec | MonadLaws | MonadLawsProp | cats.MonadSpec ]
A Monad
is a mechanism for strictly sequencing effects, also called monadic behaviour
trait Monad[F[_]] extends Applicative[F] {
// bind or >>=
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
}
- Monads for functional programming (Paper)
- First steps with monads in Scala
- Demystifying the Monad in Scala
- Cooking with Monads
- Refactoring with Monads
- Monad laws in Scala
- Monad Tutorial
Everyday monads
List
Option
Either
Identity
[ cats.IdSpec ]
Id
monad allows to call monadic methods using plain values
type Id[A] = A
MonadError
MonadError
abstracts over Either-like data types that are used for error handling and provides extra operations for raising and handling errors
trait ApplicativeError[F[_], E] extends Applicative[F] {
def raiseError[A](e: E): F[A]
def handleError[A](fa: F[A])(f: E => A): F[A]
}
trait MonadError[F[_], E] extends ApplicativeError[F, E] with Monad[F] {
def ensure[A](fa: F[A])(e: E)(f: A => Boolean): F[A]
}
Free Monad
- Stack Safety for Free (Paper)
- Stackless Scala With Free Monads (Paper)
- Move Over Free Monads: Make Way for Free Applicatives! by John de Goes (Video)
- Stackless Scala
- Free monads - what? and why?
- Free Monad examples
- Overview of free monad in cats
- Modern Functional Programming [part-1|part-2]
Data Types
It is impossible to combine two arbitrary monads (write a general definition of flatMap
) without knowing something about at least one of them. Monad Transformers to the rescue! By convention their name ends with a T
suffix
Common Monad Transformers are
OptionT
forOption
EitherT
forEither
ReaderT
forReader
WriterT
forWriter
StateT
forState
Eval
[ cats.EvalSpec ]
Eval
monad controls eager, lazy, and memoized evaluation
trait Eval[A] {
def value: A
def memoize: Eval[A]
}
One useful property of Eval
is that its map
and flatMap
methods are trampolined. This means that arbitrarily nested calls don't consume stack frames i.e. it's stack safety
Eval
monad is a useful tool to enforce stack safety when working on very large computations and data structures. Trampolining is not free although, it avoids consuming stack by creating a chain of function objects on the heap. There are still limits on how deeply computations can be nested, but they are bounded by the size of the heap rather than the stack
State
[ State | StateT | cats.StateSpec ] [ MainState | MainStateT | MainStateTLoop ]
A State[S, A]
represents a monadic wrapper of a function S => (S, A)
// IndexedStateT[Eval, S, S, A]
type State[S, A] = StateT[Eval, S, A]
// alias
type StateT[F[_], S, A] = IndexedStateT[F, S, S, A]
class IndexedStateT[F[_], SA, SB, A](val runF: F[SA => F[(SB, A)]])
State
monad allows to model mutable state in a purely functional way, without using mutation
sbt "fp/runMain com.github.niqdev.main.MainState"
sbt "fp/runMain com.github.niqdev.main.MainStateT"
sbt "fp/runMain com.github.niqdev.main.MainStateTLoop"
Writer
[ cats.WriterSpec ]
A Writer[L, V]
represents a monadic wrapper of a value (L, V)
type Writer[L, V] = WriterT[Id, L, V]
class WriterT[F[_], L, V](run: F[(L, V)])
Writer
monad lets carry a log along with a computation. It's useful to record messages, errors, or additional data about a computation, and extract the log alongside the final result
Kleisli
[ Kleisli | KleisliSpec ]
A Kleisli[F, A, B]
represents a monadic wrapper of a function A => F[B]
class Kleisli[F[_], A, B](run: A => F[B])
Kleisli
allows to compose functions that return a monadic value
Reader
[ cats.ReaderSpec ]
A Reader[A, B]
represents a monadic wrapper of a function A => B
// Kleisli[Id, E, A]
type Reader[A, B] = ReaderT[Id, A, B]
// alias
type ReaderT[F[_], A, B] = Kleisli[F, A, B]
Reader
monad allows to sequence operations that depend on some input and one common use is dependency injection
OptionT
[ cats.OptionTSpec ]
A OptionT[F[_], A]
represents a monadic wrapper of a value F[Option[A]]
class OptionT[F[_], A](value: F[Option[A]])
OptionT
is a monad transformer for Option
Validated
Validated
is similar to Either
but allows to accumulate errors
sealed abstract class Validated[+E, +A]
case class Valid[+A](a: A) extends Validated[Nothing, A]
case class Invalid[+E](e: E) extends Validated[E, Nothing]
The Semigroupal
implementation of product
for a Monad
is equivalent to flatMap
, which explains why Either
, being a Monad
provides a fail-fast error handling. On the other side, Validated
has an instance of Semigroupal
but no instance of Monad
, hence the implementation of product
is free to accumulate errors
Arrow
TODO
Comonad
TODO
Effects
Given a monad M[A]
, if you can not extract the A
out of M[A]
in a purely-functional way (for List
you can perform the extraction with List.head
) then the monad is called effectful
- Cats Effect (Documentation)
- FP to the Max by John De Goes (Video)
- Intro to Functional Game Programming
- Incremental Purity (Meetup)
- TODO Shared State in Functional Programming
IO
[ IO ] [ MainIO | effect.ExampleIO | effect.ExampleIOApp | effect.ExampleResource ]
IO
is data type for encoding side effects as pure values
- The Making of an IO by Daniel Spiewak (Video)
- An IO monad for cats
- Monadic IO: Laziness Makes You Free
TODO add more examples
sbt "fp/runMain com.github.niqdev.main.MainIO"
# cats-effect examples
sbt "ecosystem/runMain com.github.niqdev.effect.ExampleIO"
sbt "ecosystem/runMain com.github.niqdev.effect.ExampleIOApp"
sbt "ecosystem/runMain com.github.niqdev.effect.ExampleResource"
sbt "test:testOnly *MyResourceSpec"