Skip to the content.

Strong and closed functors

In Haskell, we call it a strong functor when a functor has this function strength.

class (Functor f) => ProductStrongFunctor f where
  strength :: (a, f b) -> f (a, b)

As you can see, a functor that can lift a value into a pair is a strong functor. For example, you can implement an instance of this typeclass for Maybe.

instance ProductStrongFunctor Maybe where
  strength :: (a, Maybe b) -> Maybe (a, b)
  strength (a, Just b) = Just (a, b)
  strength (_, Nothing) = Nothing

Actually, you can implement this typeclass for any functor.

instance (Functor f) => ProductStrongFunctor f where
  strength :: (a, f b) -> f (a, b)
  strength (a, fb) = fmap (\b -> (a, b)) fb

It means that any functor in Haskell is a strong functor.

To be more precise, this is left strength because it lifts a on the left. There is also right strength, but you can get it by swapping elements in a tuple in Haskell (This isn’t always true when you think about a asymmetric tensor in another category). When we talk about a strong functor, it usually means a left strong functor.

Also note that this strong functor is different from strong monoidal functor that satisfies f (a, b) and (f a, f b) are isomorphic ($F (A \otimes B) \cong (F A \otimes F B)$). We saw it in Expressing monoidal functors in Haskell.

Let’s think a bit more about a type of strength. Its type is (a, f b) -> f (a, b) which is equivalent to ((,) a) (f b) -> f (((,) a) b). When you replace (,) a with g, it’ll be g (f b) -> f (g b). As we saw in Traversable, Lone and Distributive, this holds when g is Lone, or f is Distributive. Since (,) a is Lone, we can define strength for any functor f.

We’ve defined a strong functor in terms of a pair functor (,) a, but you can define it in terms of any tensor products in a monoidal category. For example, you can define it with Either a.

class (Functor f) => CoproductStrongFunctor f where
  strength :: Either a (f b) -> f (Either a b)

Maybe is an instance of this type class, too.

instance CoproductStrongFunctor Maybe where
  strength :: Either a (Maybe b) -> Maybe (Either a b)
  strength (Left a) = Just (Left a)
  strength (Right fb) = fmap Right fb

This time, we cannot say that any functor in Haskell is a strong functor in terms of Either a because Either a isn’t Lone. But since Either a is Traversable, you can implement this typeclass for any applicative functor f.

instance (Functor f, Applicative f) => CoproductStrongFunctor f where
  strength :: Either a (f b) -> f (Either a b)
  strength (Left a) = pure (Left a)
  strength (Right fb) = fmap Right fb

Now, let’s take a look at what we can do with other functors. The type of strength was g (f b) -> f (g b) where g was (,) a. Then what will it look like when we use (->) a as g this time? We’ll get (-> a) (f b) -> f (((->) a) b) which is (a -> f b) -> f (a -> b). Let’s call a functor having this function a closed functor.

class (Functor f) => ClosedFunctor f where
  closed :: (a -> f b) -> f (a -> b)

For example, (->) r is an instance of this typeclass.

instance ClosedFunctor ((->) r) where
  closed :: (a -> (r -> b)) -> (r -> (a -> b))
  closed a2fb = \r a ->
    let fb = a2fb a
     in fb r

Will any functor be an instance of this typeclass? No, but a functor which is an instance of Distributive will be a closed functor. As we’ve just saw above, we have g (f b) -> f (g b) when f is Distributive, and we just picked (->) a as g.

instance (Functor f, Distributive f) => ClosedFunctor f where
  closed :: (a -> f b) -> f (a -> b)
  closed a2fb = distribute a2fb

Strictly speaking, a -> f b and f (a -> b) must be isomorphic for f to be a closed functor. It means that there needs to be unclosed :: f (a -> b) -> (a -> f b), where both unclosed . closed == id and closed . unclosed == id hold. But unclosed can be defined for any functors, and a pair of closed above and this unclosed satisfy these two conditions.

unclosed :: (Functor f) => f (a -> b) -> (a -> f b)
unclosed fa2b = \a -> fmap (\a2b -> a2b a) fa2b

Note that this closed functor is different from lax closed functor that has f (a -> b) -> (f a -> f b).