Errors are CoPara
Owen Lynch
Tue Feb 06 2024
This is just a quick note from a thought I had. Incidentally thoughts of this length were precisely the reason I made LocalCharts, so I’m quite happy now.
Imagine that we are working in a programming language which has the following construct. I can declare a function which either returns a value or an error of one of a certain collection of types. This is the case, for instance, in Zig, where we can have something like
fn openFile(name: String): error{FileNotFound,NotPermitted}!FileHandle {
...
}
This is a function which might fail in two ways; if I try to open a file, it might not be there, or it might be their, but I am not permitted to access it. For each of these ways it might fail, there is an appropriate error that it throws.
I can compose this with a function
fn writeFile(handle: FileHandle, contents: String): error{OutOfSpace,NotPermitted}!usize {
...
}
which might also fail in two ways. However, when I compose these functions, I get something which can throw three different types of error! I.e., I could get FileNotFound
, OutOfSpace
, or NotPermitted
.
We might model this with something like a CoPara construction. Suppose I have a set of error types E. Then I make a category where the objects are sets, and a morphism from A to B is a choice of subset E_f \subset E, and a function f \colon A \to E_f + B. Composing f \colon A \to E_f + B and g \colon B \to E_g + C gives me a function (f \circ g) \colon A \to (E_f \cup E_g) + C, where I implicitly use the map E_f + E_g \to E_f \cup E_g.
That’s sort of all there is to this thought, but @david.jaz recently said that he didn’t know any good examples of a copara construction, so here is one!
David Jaz Myers
Tue Feb 06 2024
Just to set the record straight, I said I didn’t know an example of a “dependent copara” or “dependently graded monad”. What you have here is what’s known as a graded monad, which is just a lax functor into the monoidal category of endofunctors (what’s the problem?). In particular, it is essentially the lax monoidal functor \mathcal{P}(E) \to \mathsf{Fun}(\mathsf{Type}, \mathsf{Type}) sending S to X \mapsto S + X with laxator S + T + X \to (S \cup T) + X.
You are totally right that the Kleisli construction for graded comonads coincides with the copara construction for an appropriate lax action (in this case, of the power set of E on types).
I guess since the cat is out of the bag, I should say what a dependently graded monad is. This is an idea that @mattecapu and I had as part of our abstract approach to the Para construction (note that since we talk about Para, things are dualized and we end up with comonads). We define a “fibred action” to be a pseudo-monad in a tricategory of spans with left leg a cartesian fibration and where the span morphisms are colax on the right leg; this turns out to be essentially the same thing as a “dependently graded comonad”. The dual notion — where the right leg is a cocartesian fibration and the span morphisms are lax on the left leg — gives you a “dependently graded monad”. I can describe it explicitly in Agda-like syntax:
In this case,
E
is a functor which we could think of as assigning a typeX
to the typeE X
of exceptions which computations that yield a value of typeX
could throw. Given an exceptione : E X
, we can form the typeM X e
of computations of values ofX
that might throw the exceptione
. We have “no-exception”i : E X
and a way to combine an exceptione : E X
with an exceptionf
that could be thrown aftere
and therefore on typeM X e
— that is,f : E (M X e)
— and this will give another exception onX
, namelye m f : E X
. We then have the usual monadicreturn
(orpure
) andbind
; we canreturn
the valuex : X
with no error, givingreturn x : M X i
, and we canbind
the possible errorm : M X e
to the variablex
in a lambda expression\ x -> t : X -> M Y f
which might throw an error of kindf
and get the valuebind m \ x -> t : M Y (f m (map E e))
which computes a value ofY
unlesst
through an error of kindf
orm
contained an error of kinde
, where in the latter case we must push forwarde
using the functoriality ofE
to ensure that it is a kind of error that computations of typeY
can throw.I don’t know a concrete example of that. One “problem” is that the dependency is on the codomain; naively, we would expect something to go wrong in a way that depends on the input rather than the output.
What I do know examples of are dependently graded comonads, which for the record are defined as:
There are a lot of examples of these, including things which you might imagine should be more like monadic error handling (where the dependency is on the domain i.e. input) Here’s one — the dependent Maybe comonad — in faux syntax where lacking easy unicode I’ll just write
Pair
for Sigma types (and takeunit : Unit
to be the unit type andEmpty
to be the empty type):Ok that was a lot and I hope I didn’t make any mistakes there. A Kleisli map
g : C X c -> Y
is a partial function; it can be curried intocurry g : (x : X) -> If (c x) -> Y
, so we can see it as a function that takes andx : X
and yields an element ofy
if the conditionc x : Bool
is satisfied; if it isn’t, thenIf (c x)
is empty and we can use empty-elimination to get ourselves out of this jam. This is a “dependent” version of the Maybe monad (note thatMaybe Y
is equivalent toPair b \ b -> If b -> Y
, assuming univalence); but it’s a comonad and not a monad! What? The Kleisli category of this dependently graded comonad is equivalent to the Kleisli category of the Maybe monad — but here the type signatures help you express the precise condition that you need to be true. This is a well known pattern in dependent type theory (presumably, I don’t know where to cite it from but obviously the Cubical people knew about it since Cubical sort of runs off this pattern).Anyway, there are a lot of these. You can finagle any Sigma-closed subuniverse into a dependently graded comonad like above; it also curries nicely into something more monad looking. If it is a subuniverse of propositions (i.e. a dominance), then the Kleisli category for this dep. gr. comonad is the category of partial maps with open domain.
As for another example, here’s how you can do a “cost analysis” dependently graded comonad. Suppose we have a monoid
C
,+
,0
of costs. Define the following (with lazier faux syntax):This only adds information to the type signature and that info could be happily erased; namely,
g : C X c -> Y
is just a functiong : X -> Y
, but we stipulate that it costsc x
on inputx : X
. When you do Kleisli composition, you just add the costs of each component function. By definition, the Kleisli composition ofg : C X c -> Y
andh : C Y d -> Z
ish . (extend g) : C X (\x -> c x + d (g x)) -> Z
which, as you can see, just aggregates the costs.Owen Lynch
Tue Feb 06 2024
Yet another reason I love localcharts: now I know what a graded monad is!