FP cheat sheet
As a software engineer with strong object oriented background I need a functional programming design patterns cheat sheet. Otherwise, I feel lost.
Credits:
Semigroup
Associative binary operation: combine(x, combine(y, z)) = combine(combine(x, y), z)
1
2
3
trait Semigroup[A] {
def combine(x: A, y: A): A
}
Example
1
2
3
4
5
6
7
8
import cats.kernel.Semigroup
import cats.syntax.all._
val map1 = Map("k1" -> 1, "k2" -> 1)
val map2 = Map("k1" -> 2, "k3" -> 3)
Semigroup.combine(map1, map2)
// Map(k1 -> 3, k2 -> 1, k3 -> 3)
Monoid
Sensible default to combine empty collection: combine(x, empty) = combine(empty, x) = x
1
2
3
trait Monoid[A] extends Semigroup[A] {
def empty: A
}
Functor
Type constructor
1
2
3
4
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
def lift[A, B](f: A => B): F[A] => F[B] = fa => map(fa)(f)
}
fa.map(f).map(g) = fa.map(f.andThen(g))
fa.map(x => x) = fa
Contravariant Functor
1
def contramap[A, B](fa: F[A])(f: B => A): F[B]
Invariant Functor
1
def imap[B](dec: A => B, enc: B => A): Codec[B]
Monad
Mechanism for sequencing computations
1
2
3
4
trait Monad[F[_]] {
def pure[A](value: A): F[A]
def flatMap[A, B](value: F[A])(func: A => F[B]): F[B]
}
pure(a).flatMap(func) == func(a)
m.flatMap(pure) == m
m.flatMap(f).flatMap(g) == m.flatMap(x => f(x).flatMap(g))
Identity Monad
Effect of having no effect
1
type Id[A] = A
Kleisli
Composition of functions that return a monadic value
1
2
3
4
final case class Kleisli[F[_], A, B](run: A => F[B]) {
def compose[Z](k: Kleisli[F, Z, A])(implicit F: FlatMap[F]): Kleisli[F, Z, B] =
Kleisli[F, Z, B](z => k.run(z).flatMap(run))
}
Semigroupal
Combine contexts
1
2
3
trait Semigroupal[F[_]] {
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
}
product(a, product(b, c)) == product(product(a, b), c)
Applicative Functor
1
2
3
4
5
6
7
trait Applicative[F[_]] extends Semigroupal[F] with Functor[F] {
def ap[A, B](ff: F[A => B])(fa: F[A]): F[B]
def pure[A](a: A): F[A]
def map[A, B](fa: F[A])(f: A => B): F[B] = ap(pure(f))(fa)
}
Foldable
1
2
3
4
trait Foldable[F[_]] {
def foldLeft[A, B](fa: F[A], b: B)(f: (A, B) => B): B
def foldRight[A, B](fa: F[A], b: B)(f: (A, B) => B): B
}
Traverse
1
2
3
4
5
6
7
8
trait Traverse[F[_]] {
def traverse[G[_]: Applicative, A, B]
(inputs: F[A])(func: A => G[B]): G[F[B]]
def sequence[G[_]: Applicative, B]
(inputs: F[G[B]]): G[F[B]] =
traverse(inputs)(identity)
}
For example, if F
is List
and G
is Option[Int]
:
1
2
3
4
def parseInt(s: String): Option[Int] = ???
List("1","2","3").traverse(parseInt) == Some(List(1,2,3))
List("1","a","3").traverse(parseInt) == None