Skip to the content.

A parameter type is existential, a return type is universal, part 4

We saw an adjunction of SomeF and Const in the part 1, and an adjunction of Const and AnyF in the part 2. They’re called an adjoint triple and denoted as $SomeF \dashv Const \dashv AnyF$. Let’s express them as code in Haskell.

We’ll use two categories. First category is $Hask$ where objects are types and morphisms are functions. The second category is $Hask^{Hask}$ where objects are endofunctors in $Hask$ and morphisms are natural transformations.

First, let’s define some types to express endofunctors in $Hask$ and natural transformations.

type FunctorType :: Type
type FunctorType = Type -> Type

type (~>) :: FunctorType -> FunctorType -> Type
type f ~> g = forall a. f a -> g a

Then, we’ll define two functors. The first functor is a functor from $Hask$ to $Hask^{Hask}$. We’ll call it FunctorFromHaskToHask2.

type FunctorFromHaskToHask2Type :: Type
type FunctorFromHaskToHask2Type = Type -> FunctorType

type FunctorFromHaskToHask2 :: FunctorFromHaskToHask2Type -> Constraint
class FunctorFromHaskToHask2 t where
  mapFromHaskToHask2 ::
    (Functor (t a), Functor (t b)) =>
    (a -> b) -> (t a ~> t b)

This functor maps an object in $Hask$ (Type) to an object in $Hask^{Hask}$ (FunctorType), and maps a morphism in $Hask$ (Type -> Type) to a morphism in $Hask^{Hask}$ (FunctorType ~> FunctorType).

The second functor is the opposite; $Hask^{Hask}$ to $Hask$. We’ll call it FunctorFromHask2ToHask.

type FunctorFromHask2ToHaskType :: Type
type FunctorFromHask2ToHaskType = FunctorType -> Type

type FunctorFromHask2ToHask :: FunctorFromHask2ToHaskType -> Constraint
class FunctorFromHask2ToHask t where
  mapFromHask2ToHask ::
    (Functor f, Functor g) =>
    (f ~> g) -> (t f -> t g)

This functor maps an object in $Hask^{Hask}$ (FunctorType) to an object in $Hask$ (Type), and maps a morphism in $Hask^{Hask}$ (FunctorType ~> FunctorType) to a morphism in $Hask$ (Type -> Type).

When you have Const,

type Const :: Type -> Type -> Type
newtype Const a b = MkConst a

you can make Const a an instance of Functor.

instance Functor (Const a) where
  fmap :: (b -> c) -> Const a b -> Const a c
  fmap _ (MkConst a) = MkConst a

When you look at the kind of Const, you’ll find its type is Type -> Type -> Type, but it’s identical to Type -> FunctorType. So you can think Const is a functor from $Hask$ to $Hask^{Hask}$. Let’s make it an instance of FunctorFromHaskToHask2.

instance FunctorFromHaskToHask2 Const where
  mapFromHaskToHask2 :: (a -> b) -> (Const a ~> Const b)
  mapFromHaskToHask2 a2b = \(MkConst a) -> MkConst (a2b a)

Next, let’s take a look at SomeF.

type SomeF :: FunctorType -> Type
data SomeF f = forall a. MkSomeF (f a)

SomeF is a type that takes a FunctorType. So you can think it’s a functor from $Hask^{Hask}$ to $Hask$. Let’s make it an instance of FunctorFromHask2ToHask.

instance FunctorFromHask2ToHask SomeF where
  mapFromHask2ToHask :: (f ~> g) -> (SomeF f -> SomeF g)
  mapFromHask2ToHask f2g = \(MkSomeF fa) -> MkSomeF (f2g fa)

You can do the same with AnyF.

type AnyF :: FunctorType -> Type
newtype AnyF f = MkAnyF (forall a. f a)

instance FunctorFromHask2ToHask AnyF where
  mapFromHask2ToHask :: (f ~> g) -> (AnyF f -> AnyF g)
  mapFromHask2ToHask f2g = \(MkAnyF fa) -> MkAnyF (f2g fa)

Now, we’ve had necessary functors; Const from $Hask$ to $Hask^{Hask}$, and SomeF and AnyF from $Hask^{Hask}$ to $Hask$. We’ll define two type classes expressing adjunctions. The first one has $Hask^{Hask}$ to $Hask$ on the left, and $Hask$ to $Hask^{Hask}$ on the right.

class
  (FunctorFromHask2ToHask f, FunctorFromHaskToHask2 g) =>
  LeftAdjunction f g
    | f -> g,
      g -> f
  where
  leftLeftAdjunct :: (Functor h) => (f h -> a) -> (h ~> g a)
  rightLeftAdjunct :: (Functor h) => (h ~> g a) -> (f h -> a)

The second one has the opposite.

class
  (FunctorFromHaskToHask2 f, FunctorFromHask2ToHask g) =>
  RightAdjunction f g
    | f -> g,
      g -> f
  where
  leftRightAdjunct :: (Functor h) => (f a ~> h) -> (a -> g h)
  rightRightAdjunct :: (Functor h) => (a -> g h) -> (f a ~> h)

We can make a pair of SomeF and Const an instance of LeftAdjunction, and a pair of Const and AnyF an instance of RightAdjunction.

instance LeftAdjunction SomeF Const where
  leftLeftAdjunct :: (Functor h) => (SomeF h -> a) -> (h ~> Const a)
  leftLeftAdjunct someH2a = \h -> MkConst (someH2a (MkSomeF h))

  rightLeftAdjunct :: (Functor h) => (h ~> Const a) -> (SomeF h -> a)
  rightLeftAdjunct h2const = \(MkSomeF h) -> let MkConst a = h2const h in a

instance RightAdjunction Const AnyF where
  leftRightAdjunct :: (Functor h) => (Const a ~> h) -> (a -> AnyF h)
  leftRightAdjunct const2h = \a -> MkAnyF (const2h (MkConst a))

  rightRightAdjunct :: (Functor h) => (a -> AnyF h) -> (Const a ~> h)
  rightRightAdjunct a2anyH = \(MkConst a) -> let MkAnyF ha = a2anyH a in ha

In an adjoint triple, there is a forgetful functor in the middle, and a free functor on the left and a cofree functor on the right. A forgetful functor forgets a structure, a free functor creates a minimum structure, and a cofree functor creates a maximum structure.

In this adjoint triple $SomeF \dashv Const \dashv AnyF$, you can think that Const forgets a functor f, SomeF creates a minimum functor that works with some given a, and AnyF creates a maximum functor that works with any given a.