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 -| Const -| 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
  liftLeftAdjunct :: (Functor h) => (f h -> a) -> (h ~> g a)
  leftRightAdjunct :: (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
  liftRightAdjunct :: (Functor h) => (f a ~> h) -> (a -> g h)
  rightLeftAdjunct :: (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
  liftLeftAdjunct :: (Functor h) => (SomeF h -> a) -> (h ~> Const a)
  liftLeftAdjunct someH2a = \h -> MkConst (someH2a (MkSomeF h))

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

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

  rightLeftAdjunct :: (Functor h) => (a -> AnyF h) -> (Const a ~> h)
  rightLeftAdjunct 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 -| Const -| 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.