Invariant Shadows - Part 2: Monoidals

In the last post, we built type classes for a subset of invariant functors – namely, type classes that drew inspiration from the covariant FlatMap and Monad . In this article, we’ll explore invariant shadows of the type classes supporting applicative functors.

Disclaimer: I am not a category theorist, and in both the previous article and this article, I use fairly loose reasoning to explore these type classes. Corrections are welcome.

First, let’s review the definition of an applicative functor, using a type class hierachy similar to the one used in Cats. (These examples are simplified versions of the definitions in the Cats codebase.)

trait Apply[F[_]] extends Functor[F] { def ap[A, B](fa: F[A])(ff: F[A => B]): F[B] } trait Applicative[F[_]] extends Apply[F] { def pure[A](a: A): F[A] def map[A, B](fa: F[A])(f: A => B): F[B] = ap(fa)(pure(f)) }

Along with laws:

def applyComposition[F[_], A, B, C](fa: F[A], fab: F[A => B], fbc: F[B => C])(implicit F: Apply[F]): Boolean = { val compose: (B => C) => (A => B) => (A => C) = _.compose F.ap(F.ap(fa)(fab))(fbc) == F.ap(fa)(F.ap(fab)(F.map(fbc)(compose))) } def applicativeIdentity[F[_], A](fa: F[A])(implicit F: Applicative[F]): Boolean = F.ap(fa)(F.pure((a: A) => a)) == fa def applicativeHomomorphism[F[_], A, B](a: A, f: A => B)(implicit F: Applicative[F]): Boolean = F.ap(F.pure(a))(F.pure(f)) == F.pure(f(a)) def applicativeInterchange[F[_], A, B](a: A, ff: F[A => B])(implicit F: Applicative[F]): Boolean = F.ap(F.pure(a))(ff) == F.ap(ff)(F.pure((f: A => B) => f(a))) def applicativeMap[F[_], A, B](fa: F[A], f: A => B)(implicit F: Applicative[F]): Boolean = F.map(fa)(f) == F.ap(fa)(F.pure(f))

Applicative[Codec]

Can we define an Applicative[Codec] instance? We’ve previously seen how Codec has an invariant functor instance but not a covariant functor instance. As such, we can rule out an Applicative[Codec] , for if we could define such an instance, it would give rise to a Functor[Codec] . Regardless, let’s try to implement such an instance, as it is illustrative.

In the last article, it was implied that we can implement pure for Codec . Here’s an implementation:

def pure[A](a: A): Codec[A] = new Codec[A] { def sizeBound = SizeBound.exact(0) def encode(a: A) = Attempt.successful(BitVector.empty) def decode(b: BitVector) = Attempt.successful(DecodeResult(a, b)) }

The encode operation always returns an empty vector, while the decode operation always returns the full input as the remainder and returns the pure value as the decoded value.

How about ap ?

def ap[A, B](ca: Codec[A])(cf: Codec[A => B]): Codec[B] = new Codec[B] { def sizeBound = SizeBound.unknown def decode(b: BitVector) = (for { decA <- DecodingContext(ca) defF <- DecodingContext(cb) } yield decF(decA)).decode(b) def encode(b: B) = { ??? } }

The decode case is easy enough to define using the DecodingContext type from scodec, which is equivalent to a StateT[Attempt, BitVector, ?] monad transformer stack, threading the remainder of each decode as the state value. We encounter an impasse in encode though. We could try to add a B => A function, though it is not obvious as to how that would help. Instead, we can use an alternative form of applicative functors known as monoidal functors.

Monoidal Functors

(Lax) monoidal functors are an alternative form of applicative functors – similar to how monads can be represented with either pure and flatMap or unit , flatten (aka join ), and map .

trait Monoidal[F[_]] extends Functor[F] { def unit: F[Unit] def zip[A, B](fa: F[A], fb: F[B]): F[(A, B)] }

Instead of defining pure and ap , monoidal functors are defined in terms of unit , zip , and map . Note that in the standard/typical definition, map is derived from pure and ap but in the monoidal definition, map is left abstract.

The laws for monoidal functors are the following:

trait Iso[A, B] { def apply(a: A): B } implicit class IsoOps[A](val a: A) { def ~=[B](b: B)(implicit iso: Iso[A, B]): Boolean = iso(a) == b } def monoidalLeftIdentity[F[_], A](fa: F[A])(implicit F: Monoidal[F], iso: Iso[F[(Unit, A)], F[A]]): Boolean = F.zip(F.unit, fa) ~= fa def monoidalRightIdentity[F[_], A](fa: F[A])(implicit F: Monoidal[F], iso: Iso[F[(A, Unit)], F[A]]): Boolean = F.zip(fa, F.unit) ~= fa def monoidalAssociativity[F[_], A, B, C](fa: F[A], fb: F[B], fc: F[C])(implicit F: Monoidal[F], iso: Iso[F[(A, (B, C))], F[((A, B), C)]]): Boolean = F.zip(fa, F.zip(fb, fc)) ~= F.zip(F.zip(fa, fb), fc)

The identity laws state that zipping a value with the unit value yields the original value, ignoring the tuple structure. The associativity law requires zip to be associative, again ignoring the tuple structure. Here, like in the Typeclassopedia definition, we defer the equality checking to an isomorphism that ignores the unwanted structure.

We could further generalize this type class by removing the requirement for the unit and map operations, leaving simply zip along with the associativity law – this is exactly what Scalaz has done with the scalaz.Zip type class.

Converting applicative forms

We can convert any applicative functor to a monoidal functor and vice-versa:

implicit def applicativeToMonoidal[F[_]](implicit F: Applicative[F]): Monoidal[F] = new Monoidal[F] { def unit: F[Unit] = F.pure(()) def zip[A, B](fa: F[A], fb: F[B]): F[(A, B)] = F.ap(fb)(F.map(fa)(a => (b: B) => (a, b))) def map[A, B](fa: F[A])(f: A => B): F[B] = F.map(fa)(f) } implicit def monoidalToApplicative[F[_]](implicit F: Monoidal[F]): Applicative[F] = new Applicative[F] { def pure[A](a: A): F[A] = F.map(F.unit)(_ => a) def ap[A, B](fa: F[A])(ff: F[A => B]): F[B] = F.map(F.zip(fa, ff)) { case (a, f) => f(a) } }

Monoidal[Codec]

Let’s try to implement a Monoidal instance for Codec .

The definition of unit looks very similar to the definition of pure :

def unit: Codec[Unit] = new Codec[Unit] { def sizeBound = SizeBound.exact(0) def encode(a: A) = Attempt.successful(BitVector.empty) def decode(b: BitVector) = Attempt.successful(DecodeResult((), b)) }

Now let’s try to implement zip – note that we should not be able to, as we know Codec doesn’t have a covariant functor.

def zip[A, B](ca: Codec[A], cb: Codec[B]): Codec[(A, B)] = new Codec[(A, B)] { def sizeBound = ca.sizeBound + cb.sizeBound def decode(b: BitVector) = (for { a <- DecodingContext(ca) b <- DecodingContext(cb) } yield (a, b)).decode(b) def encode(ab: (A, B)) = for { encA <- ca.encode(ab._1) encB <- cb.encode(ab._2) } yield encA ++ encB }

Wait, so what are we missing? We have an implementation of unit and zip and yet we know we can’t have a lawful Monoidal[Codec] instance due to the fact that there’s no covariant functor for Codec . Monoidal functors have three abstract operations though, not two (like Applicative ) – unit , zip , and map . So in order to have a monoidal functor for Codec , we need to define map , which we know we can’t do!

However, we do have xmap . What happens if we pair xmap with unit and zip ?

Invariant monoidal functors

Let’s define a new invariant shadow of Monoidal that extends InvariantFunctor instead of Functor :

trait InvariantMonoidal[F[_]] extends InvariantFunctor[F] { def unit: F[Unit] def zip[A, B](fa: F[A], fb: F[B]): F[(A, B)] }

We can also port the laws from lax monoidal functors, which are identical except for requiring an InvariantMonoidal instance instead of a Monoidal instance:

def xmonoidalLeftIdentity[F[_], A](fa: F[A])(implicit F: InvariantMonoidal[F], iso: Iso[F[(Unit, A)], F[A]]): Boolean = F.zip(F.unit, fa) ~= fa def xmonoidalRightIdentity[F[_], A](fa: F[A])(implicit F: InvariantMonoidal[F], iso: Iso[F[(A, Unit)], F[A]]): Boolean = F.zip(fa, F.unit) ~= fa def xmonoidalAssociativity[F[_], A, B, C](fa: F[A], fb: F[B], fc: F[C])(implicit F: InvariantMonoidal[F], iso: Iso[F[(A, (B, C))], F[((A, B), C)]]): Boolean = F.zip(fa, F.zip(fb, fc)) ~= F.zip(F.zip(fa, fb), fc)

Deriving an InvariantMonoidal from an InvariantMonad

In the same way that a monad yields two monoidal functors (or two applicative functors) – one that evaluates F[A] first and F[B] second, and another which evaluates in the reverse order – an invariant monad gives rise to two invariant monoidal functors. For example, the following implementation evaluates the F[A] first and the F[B] second:

implicit def xmonadToXmonoidal[F[_]](implicit F: InvariantMonad[F]): InvariantMonoidal[F] = new InvariantMonoidal[F] { def unit: F[Unit] = F.pure(()) def zip[A, B](fa: F[A], fb: F[B]): F[(A, B)] = F.xflatMap(fa)(a => F.xmap(fb)(b => (a, b))(ab => ab._2))(ab => ab._1) def xmap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] = F.xmap(fa)(f)(g) }

Sharing unit/zip

Because the invariant monoidal differs from the covariant monoidal only in the defintion of xmap / map , we could extract a type class that captures the signature of unit and zip and the laws that govern their interaction.

trait MonoidalBase[F[_]] { def unit: F[Unit] def zip[A, B](fa: F[A], fb: F[B]): F[(A, B)] } trait Monoidal[F[_]] extends MonoidalBase[F] with Functor[F] trait InvariantMonoidal[F[_]] extends MonoidalBase[F] with InvariantFunctor[F]

Applicability to Codec

In the last article, we saw that Codec supported both flatZip and consume , and how those methods exist due to the invariant monad structure of Codec . In fact, those methods both existed before the invariant monad structure was extracted. Is the same true for unit and zip ? That is, do those methods exist under some other name, their existence justified by their usefulness in binary codec creation, rather than satisfying a type class definition?

It turns out that both of these operations do exist already. The unit operation is provided by scodec.codecs.ignore(0L) , which returns a Codec[Unit] that encodes an empty bit vector. The zip operation is provided by the pairedWith method on codec – which has the operator alias ~ .

We did not provide an invariant shadow of applicative functors, though that is worth exploring, perhaps in a future article.