Bartosz Milewski's Programming Cafe

bartoszmilewski.com · 2026-07-14 · by Long Nguyen ·generic
Previously: Kan Extensions in Double Categories. In programming, actegories play a central role in optics: lenses, prisms, traversals, etc. To understand actegories, let's start with the definition of a monoidal category. Monoidal Category A monoidal category $latex \mathbf M$ is a category equipped with a tensor product. A tensor product is a functor $latex \otimes…

Bartosz Milewski's Programming Cafe

June 30, 2026

Actegories

Posted by Bartosz Milewski under Category Theory, Haskell | Tags: Actegories, Category Theory, Haskell, Lens, Optics | Leave a Comment

Previously: Kan Extensions in Double Categories.

In programming, actegories play a central role in optics: lenses, prisms, traversals, etc. To understand actegories, let’s start with the definition of a monoidal category.

Monoidal Category

A monoidal category \mathbf M is a category equipped with a tensor product. A tensor product is a functor \otimes \colon \mathbf M \times \mathbf M \to \mathbf M. We assume that this product is associative and unital– up to isomorphism. It means that there is an invertible associator:

\alpha_{a, b, c} \colon (a \otimes b) \otimes c \to a \otimes (b \otimes c)

natural in all three arguments. We also have a unit object 1 and two (invertible, natural) unitors:

\lambda_a \colon 1 \otimes a \to a

\rho_a \colon a \otimes 1 \to a

To get a better feel for it, we can try to model a monoidal category in Haskell. We parameterize it by the type of the tensor product, `ten`, which we want to be a `Bifunctor`:

class (Bifunctor ten) => MonoidalCategory ten where ...

The standard way to define a subcategory of Hask is to restrict the types of objects by imposing a constraint. Such a restriction has a special kind, `Constraint`:

class Bifunctor ten  => MonoidalCategory (obj :: Type -> Constraint) ten where ...

A common example of such a constraint is a typeclass. For instance `Monoid` will restrict the objects of the category to be monoids. (In principle, we should also restrict the type of arrows, here to monoid morphisms.)

We can specify the unit of a monoidal category as an associated type (parameterized by `ten`):

type Unit ten :: Type

The unit should be an object of the category, so it should satisfy the constraint. We can encode this in our definition as a precondition: `obj (Unit ten)`. This leads to a circularity, which we can overcome using the language pragma `UndecidableSuperClasses`:

class (Bifunctor ten , obj (Unit ten))  => MonoidalCategory (obj :: Type -> Constraint) ten where    type Unit ten :: Type    ...

Finally, we can add the associator and the unitors (and their inverses):

class (Bifunctor ten , obj (Unit ten))  => MonoidalCategory (obj :: Type -> Constraint) ten where    type Unit ten :: Type    alpha  :: (obj a, obj b, obj c) =>  (a `ten` b) `ten` c -> a `ten` (b `ten` c)    lambda :: (obj a) => (Unit ten) `ten` a -> a    ...

Notice the `obj` constraints in the type of these functions and the infix notation for the tensor.

Let’s work out a few examples. The simplest is the category of all types with a cartesian product as tensor.

instance MonoidalCategory Hask (,) where    type Unit (,) = ()    alpha ((a, b), c) = (a, (b, c))    lambda ((), a) = a    ...

We define `Hask` using an empty class, and we make all objects its instances:

class Hask ainstance Hask a

Similarly, we can define a monoidal category with `Either` as the tensor product, or with `Monoid` as the object constraint.

Actegory

An actegory is a category that supports the action of a monoidal category. You may think of it as “multiplying” or “scaling” the objects of this category by objects of the monoidal category. The (left) action can be defined as a functor from the product category to C:

\triangleright \colon \mathbf M \times C \to C

or, after currying, as a functor from \mathbf M to the endofunctor category:

![\triangleright \colon \mathbf M \to [C, C]](https://s0.wp.com/latex.php?latex=%5Ctriangleright+%5Ccolon+%5Cmathbf+M+%5Cto+%5BC%2C+C%5D&bg=ffffff&fg=29303b&s=0&c=20201002)

The coherency conditions are the invertible natural transformations that relate the action \triangleright to the tensor product \otimes and its unit 1:

\alpha_{m n a} \colon (m \otimes n) \triangleright a \to m \triangleright (n \triangleright a)

\lambda_{a} \colon 1 \triangleright a \to a

The action is functorial in both arguments, so our Haskell translation pegs it, for simplicity, as a `Bifunctor`. (A `Profunctor` action is also possible. Categorically, it would correspond to using \mathbf M^{op} as the monoidal category.)

class (MonoidalCategory obj ten, Bifunctor act)   => Actegory obj ten act | act -> ten where    assoc  :: (obj m, obj n)            => (m `ten` n) `act` a -> m `act` (n `act` a)    assoc' :: (obj m, obj n)           => m `act` (n `act` a) -> (m `ten` n) `act` a    unit   :: Unit ten `act` a -> a    unit'  :: a -> Unit ten `act` a

Another simplifying assumption is that the action uniquely identifies the tensor product, encoded here as the functional dependency `act -> ten`.

The simplest example of an actegory is the self action of the cartesian product. Here, the monoidal category acts on itself:

instance Actegory Hask (,) (,) where    assoc ((m, n), a) = (m, (n, a))    assoc' (m, (n, a)) = ((m, n), a)    unit ((), a) = a    unit' a = ((), a)

Monoidal Functors

Actegories that use the same monoidal category for their actions form a category. The morphisms in this category are (strict) monoidal functors. These are functors that map one action to another:

f (m \triangleright_1 a) \cong m \triangleright _2 f a

In Haskell, we can model them as:

class (Actegory obj ten act1, Actegory obj ten act2, Functor f) =>  MonFunctor obj ten act1 act2 f where    as  :: obj m => m `act2` f a   -> f (m `act1` a)    as' :: obj m => f (m `act1` a) -> m `act2` f a

In fact, actegories form a bicategory, with action-preserving natural transformations acting between monoidal functors.

Here’s an interesting example of a monoidal functor between non-trivial actegories:

instance (Traversable f) => MonFunctor Monoid (,) (,) (,) f where    as (m, fa) = fmap (m, ) fa    as' = sequenceA

Haskell code is available here.

Share this:

Powered by WordPress.com.

Discover more from Bartosz Milewski's Programming Cafe

Subscribe now to keep reading and get access to the full archive.

Type your email…

Subscribe

Continue reading

%d

bartoszmilewski.com · 15 images · 19 links

Comments (0)

No comments yet