There are a couple of basic constructions from math the end up being data types in (for example) cats in scala. Unfortunately, the names of the data types tend to obscure the underlying mathematical construction IMHO. This is meant to be a short description of a few data types in terms of products, co-products and functions.
In scala the Tuple2
aka (?,?)
is the canoncical product of two types. This is a covariant functor in both of its type parameters. Here's map in the second parameter (for (A,?)
).
def map[A,B,C](x: (A,B), f: B => C): (A,C) = (x._1, f(x._2))
If the first parameter is a semigroup then we can define flatten
and it is therefore a monad. The writer monad.
def flatten[A: Semigroup, B](x: (A, (A, B))): (A, B) = (x._1 |+| x._1._1, x._1._2)
Yes the code is ugly, but we just combine the only two A
s that we have; we can do this because A
is a Semigroup
.
In scala, Function1
aka ? => ?
is the Hom functor. It is a contravariant functor in the first argument and covariant in the second argument. Here's map in the second argument (for A => ?
).
def map[A,B,C](x: A => B, f: B => C): A => C = f compose x
We can define flatten
making it a monad. The reader monad.
def flatten[A,B](x: A => A => B): A => B = { a => x(a)(a) }
In terms of functors this is the composition of Reader and Writer, i.e. A => (A, ?)
but we need a definition of flatten
to make it a monad. The state monad
def flatten[A,B](x: A => (A, A => (A,B))): A => (A, B) = { a =>
val (nextA, f) = x(a)
f(nextA)
}
In scala Either[A,B]
is the canonical coproduct. It is a covariant functor in both of its type parameters. Here's map in the second parameter (For Either[A,?]
.
def map[A,B,C](x: Either[A,B], f: B => C): Either[A,C] = x match {
case Right(b) => Right(f(b))
case Left(a) => Left(a)
}
We can define flatten
and therefore have a monad.
def flatten[A, B](x: Either[A, Either[A, B]]): Either[A, B] = x match {
case Left(a) => Left(a)
case Right(Left(a)) => Left(a)
case Right(Right(b)) => Right(b)
}
Same as Either
but it's main use is when the left type is a semigroup, which allows a nicer Applicative
. The gist is that errors are on the left and we want to accumulate them for anything that happens in parallel.
Note that even though it's technically a monad, in cats they call flatMap
andThen
because the ap
that you get for free with flatMap
is different that this one that relies on the semigroup structure of A
.
I'll write it in terms of Either
.
def ap[A: Semigroup, B, C](x: Either[A, B], ef: Either[A, B => C]): Either[A,C] = (x, ef) match {
case (Right(b), Right(f)) => Right(f(b))
case (Left(a1), Left(a2)) => Left(a1 |+| a2) // Combine multiple errors.
case (Left(a), _) => Left(a)
case (_, Left(a)) => Left(a)
}