Complexity bounds for container functors and comonads

The notion of containers , due to Abbott et al. , characterises a subset of parametric data types which can be described by a set of shapes and a set of positions for each shape. This includes common data types such as tuples, lists, trees, arrays, and graphs. Various useful categorical structures can be derived for containers that have some additional structure on their shapes and positions. For example, the notion of a directed container (due to Ahman et al. ) gives rise to container comonads. Containers, and reﬁnements such as directed containers, provide a useful reasoning tool for data types and an abstraction mechanism for programming, e.g. , building libraries parameterised over containers. This paper studies the performance characteristics of traversal schemes over containers modelled by additional functor and comonad structure. A cost model for container transformations is deﬁned from which complexity bounds for the operations of container functors and comonads are derived. This provides a reasoning principle for the performance of programs structured using these idioms, suggesting optimisations which follow from the underling mathematical structure. Due to the abstract interface provided by the syntax of containers and category theory, the complexity bounds and subsequent optimisations they imply are implementation agnostic (machine free). As far as we are aware, this is the ﬁrst such study of the performance characteristics of containers.


Introduction
Consider the following two program fragments, written in some imperative language, where A, B, and C are one-dimensional arrays and f and g are pure functions: The two programs are extensionally equivalent; given the same inputs, they compute the same result. However, the two programs are not intensionally equivalent; the left-hand program has worse performance. This inefficiency is revealed by an execution-time analysis. is dominant, the optimisation does not make the complexity any worse. This paper studies a generalisation of the above example and reasoning (and we return to the above example later, Example 6, p. 15). We consider a general class of parametric data types known as containers, as characterised by Abbott et al. [1,2] (which includes arrays, lists, labelled trees and graphs) and consider functor and comonad structures which model common traversal schemes over containers akin to map and gather patterns. We give complexity bounds for the operations of container functors and comonads. From these complexity bounds, the usual axioms of functors and comonads can be converted to optimising rewrite rules which can be exploited by a programmer or compiler (or by a library which can specify rules to the compiler, e.g., as provided by the Glasgow Haskell Compiler's rewrite rules system [19]).
We study functors since they capture the common programming pattern of an element-wise traversal of a data structure, e.g., the map function on lists, or transforming every element of an array [21]. We study comonads since they generalise a functor's traversal, capturing local transformations [17,18,23] which may depend on an element and its neighbours, (often called a gather [9]), e.g., convolutions, fluid simulations, and cellular automata such as the Game of Life. We study containers with additional structure, called directed containers, which induce a comonad for the container, as introduced by Ahman, Chapman, and Uustalu [3]. The novelty in this work is the analysis of the complexity of these derived functor and comonad structures on containers.
Our general motivation is to provide a basis for deriving compiler optimisations on containerbased programs. To this end, we consider two varieties of functor/comonad structure throughout: (1) those derived from (directed) container structures and (2) those that are abstract (non-derived), where the implementation is unknown (as in an abstract data type). An example abstract situation may be a piece of code parameterised by a (container) functor, e.g., an overloaded Haskell function of type f :: (Container c, Functor c) => c a -> ..., where we wish to reason about complexity of f regardless of the unknown instantiation of the container functor c. In the abstract case, complexity bounds similar to those for the derived structure can be calculated, but with some approximation. This provides a kind of implicit-complexity measure, which is made explicit in the derived structure where the implementation is given. Complexity measures in the abstract setting enable optimising compilation even in the presence of abstract container parameters, or as-yet-unknown implementations of containers and their associated structures.
The following section introduces preliminary definitions of containers, container cost analysis, and notation. Section 3 and Section 4 study container functors and comonads, and their complexity. Section 5 considers related work and Section 6 concludes with some further discussion.

Containers
The notion of a container corresponds to parametric data types that contain only strictly positive occurrences of the parameter type. An alternate characterisation is that containers comprise a set of shapes and a set of positions for each possible shape [1,2]. Since we study complexity bounds on operations over containers, we restrict ourselves to a subset of containers that have finite sets of positions for every shape (but the number of shapes may be infinite) We introduce here the main definitions, but more detail can be found in the work of Abbott et al. [1,2]. Section 4 introduces the additional structure of directed containers. All definitions are made in some base category C assumed to be Cartesian closed and locally Cartesian closed. The λ-calculus is therefore used as the internal language of C for convenience.

Definition 1 (Containers).
A container comprises a set of shapes S : Set and a shape-indexed family of positions P : S → Set, often written as the pair S ⊳ P . Shapes can be thought of as templates for a data structure where positions identify the "holes" that can be filled with data.
For the sake of generality, we can replace Set with C above, situating a container within some arbitrary (locally) Cartesian closed category. The reader may instantiate C to Set if they wish to think concretely, and our examples are all within Set.

Example 1 (Lists).
A running example will be the data type of lists, defined by the container N ⊳ Fin. Shapes are natural numbers, corresponding to the length of a list, and positions are drawn from finite sets, where for a shape s : N positions are Fin s = {0, . . . , s − 1}.
Definition 2 (Container data type). From a container S ⊳ P , a parametric data type (object mapping on C) is defined S ⊳ P A = Σs : S.(P s → A) as a dependent sum of a shape s : S and a valuation from positions in the shape (taken from P s) to values A. These values are called the elements of the container. Throughout, a tuple notation (s, v) will be used for inhabitants of the dependent sum of a container type, with shape s : S and valuation v : P s → A. These tuples (s, v) will be referred to as container values.
Definition 3 (Finite containers). A container S ⊳ P is finite if for every shape s : S its position set P s is finite. That is, there is a natural transformation size A : S ⊳ P A → N, defined: where the size of a container value is the cardinality of the position set for its shape. Finiteness necessarily implies that the set of positions is discrete.
Lists are finite containers since for every shape s : N (length) the set of positions is the finite set {0, . . . , s − 1} (see Example 1 above). Note that lists are still finite containers despite there being an infinite number of shapes. Other common containers in programming (trees, arrays) follow a similar scheme: for a given shape there are a finite number of positions.
Example 2 (Streams). The stream container has a single shape, denoted * , whose positions are drawn from the natural numbers, i.e., Stream = { * } ⊳ (const N). Thus, streams are not finite containers; streams are infinite.
Finite containers relate to Girard's definition of a normal functor (representable by a power series) [8], which are equivalent to container functors with a countable set of shapes and finite sets of positions for each shape [2]. Whilst the above definition of finite containers is not restricted to countably many shapes, this is sufficient for our purposes since we need not enumerate all shapes. In any case, all the examples in this paper happen to have countable shapes since this is common in programming.
For brevity, the word container will refer throughout to finite containers unless explicitly stated otherwise. We restrict the development to finite containers since there is a clear notion of input size which give finite bounds on the complexity of whole-structure traversals. We discuss infinite containers briefly in Section 6, using the stream container example.

Example 3 (Real functions).
For completeness sake, an example of an infinite and non-discrete (continuous) container is the container of multi-dimensional real-valued functions N ⊳ (λn.R n ) where shapes count the number of dimensions and positions are vectors of real numbers. We do not consider complexity bounds for such containers since there is no natural measure of input size.

Cost semantics
A cost model for container values is key to the results of this paper.
For a function f : A → B, its execution time on inputs A of size n will be denoted [f ] n . We do not necessarily have a model for the execution time of every f in the underlying category, nor a size function for A values. Therefore, in some cases, this execution time will be left abstract. However, functions on containers op : S ⊳ P A → B will have their execution time [op] n given concretely by a cost model function [−] which maps container terms to N, where: The costing function [−] is recursively defined over the syntax of terms used to define container operations op which is a subset of λ-terms (Proposition 1): Proposition 1. Container operations op (mapping from container values to container values) are defined in this paper in terms of the following sub-grammar of λ-calculus terms t extended with position terms p, shape terms s, and abstract functions f which are either parameters to op or input valuations v: That is op(s, v) = t where terms t are either container values (s, t), variables, λ-terms abstracting over positions (where position variables are denote x p ), position terms, β-redexes on abstract functions or valuations f , or β-redexes of a λ-term with a position.
Definition 4 (Cost model). The cost function [−] : t → N assigns a cost to container operations. The cost for input valuations or any (universally quantified) function f that parameterises a container operation op is kept abstract in terms of [f ] n . This provides a flexible cost semantics that can be instantiated for particular abstract-machine models via instantiations of [−] n . The only assumption we place on [−] n is that it is monotonic on its input size. The costing is defined: The first clause gives a cost analysis of a container value (s, t) : Σs : S.(P s → A) which enumerates every position for the shape s, summing the cost of evaluating the container valuation t at each position. Therefore, given a container value (s, v) of size n = |P s| (number of positions at the present shape), if ∀p : P s.
Variables, position terms, and naked λ-terms cost nothing. Thus complexity measures in this paper do not take into account the different sizes of positions, which are instead uniformly treated as having a constant unit size |p| = 1.
The application of an abstract function or valuation f to a term t is defined using the parameter costing [−] n . This connects the concrete costing function to the abstract computation-time, where the cost of application is [f ] |t| (parameterised by the size of its argument) plus the evaluation cost of t and plus 1 to mark the computation step involved in the β-reduction of the application. A similar costing is used by Wadler [25, §3] for strict timing analysis, where the cost of function application is the cost of the function body plus one, plus the cost of the arguments.
The cost of a β-redex with a position argument has the cost of one plus the cost of the reduced term (with syntactic substitution [p/x p ] of the position p for variable x p ). This matches the idea that positions are of constant size and incur only a constant cost when substituted.
Counting β-reductions is known to be an imprecise cost model for the λ-calculus as the number of syntactic substitutions required is not constant and the size of substituted terms is not accounted for (see the discussion in [6]). However, in the above definition, the abstract costing [−] n provides the main cost and can be instantiated with some more precise abstract machine model. The counting of β-reductions is not used ubiquitously in the costing, only to account for applications to position constants or applications of abstract (parameter) functions. The key point of the costing [−] is to allow the cost of a valuation to be scaled by the number of elements in the container. As shall be noted throughout, the counting of β-reductions incurred by f t or (λx.t) p ends up being subsumed by the costs of the abstract functions f being scaled by a function of the input size n.
Definition 5 (Structural sizes). For nested data structures, the notation n[≤ m] denotes a container value of size n whose elements are bounded above by size m. This is similar to the notation of Skillicorn and Cai who write the size of a nested data structure as a list of elements [n, m] meaning the outer layer has size n and the inner elements of size at most m [22].
Similarly, n[m ≤] denotes a container value of size n whose elements are bounded below by size m (i.e., at least of size m).

Proposition 2.
Consider an extensional equality f ≡ g between two program fragments f and g (that is, they have the same output for the same input). If [f ] n ∈ Ω([g] n ) (or equivalently [g] n ∈ O([f ] n )) then the equality can be oriented as a rewrite rule from left to right as f g which improves the asymptotic complexity of the program. Thus this rewrite rule may provide a program optimisation.
Section 3 and Section 4 study functor and comonad structures on containers. In both, we consider derived instances of this structure, where the definition is given explicitly, and abstract instances of the structure, where the definition is unknown. For both functors and comonads, we characterise what it means to be derived and abstract. In the case of abstract container comonads, we can still reason about complexity based on the axioms of the comonad structure.

Functors
We consider containers which have the additional structure of a functor. The morphism mapping of a container functor captures the common programming pattern of applying a function to every element in a container data type, generalising the map combinator for lists. Definition 6 (Derived container functor). Every container S ⊳P induces an (endo)functor S ⊳ P : C → C (for some base category C), defined on objects and morphisms respectively by: Thus, the morphism mapping is the post-composition of the morphism f : A → B with the valuation, giving a container value S ⊳ P B of transformed elements.
Note that the composition f • v can be equivalently expressed as λp.f (v p), i.e., a term in the restricted subset of the λ-calculus used to construct valuations (see Proposition 1).
Theorem 1 (Container functor cost). For a container S ⊳ P , the morphism mapping of its functor F = S ⊳ P has complexity: for all f : A → B and for some cost function Q n of the input size n which characterises the time to compute valuations at each position (the indexing time). Thus, the execution time of a morphism mapping is linear in n times the cost of f (the transformation) plus the cost of indexing.
Proof. Following from the definition of container functors (Definition 6) and the cost function (Definition 4, p. 4), then for some (s, v) : S ⊳ P A where n = |P s| and ∀p : P s. |v p| ≤ m (i.e., elements have size of at most m), then: Note, whilst [v] 1 is parameterised by constant input size 1, its cost depends also on the container value size n since v : P s → A, hence the term Q is parameterised by the input size n.

Remark 1.
The cost to reduce the term (λp.(f (v p)) p in the resulting valuation is given as 3n by our costing function. However, this cost is subsumed by the costs n[f ] m and n[v] 1 .

Corollary 1.
Theorem 1 assumes that the elements of the input container have a well-defined size which is at most m. If there is no such size defined for the elements, the theorem (and its proof) still holds but with m = 1, i.e., The result of Theorem 1 is straightforward from the definition and well-known, at least tacitly, for concrete instances of containers such as lists, trees, and other inductive types (where Q n is constant). However, in the context of an optimising compiler, the implementation of a container functor may be unknown, rather than derived as in Definition 6. Whilst a container functor may be extensionally equivalent to that of Definition 6, it may differ intensionally. For example, an inefficient implementation for lists might use a linear-time lookup for each element (based on its index), rather than the usual recursive definition; or, lists with repeated elements might be stored more efficiently with a run-length encoding style implementation. However, we can still derive useful complexity bounds even in this abstract case, with some restriction on what it means to be an abstract finite container functor. Definition 7. An endofunctor F : C → C is an abstract finite container functor if there exists a finite container S ⊳ P such that F ∼ = S ⊳ P , that is there exists a natural isomorphism, with α A : FA → S ⊳ P A and inverse α −1 A : S ⊳ P A → FA. Remark 2. An abstract finite container value has a definition of size via the isomorphism to some concrete finite container. That is, We first show a lower bound for such abstract container functors in the worst case. Proposition 3. Let F be an abstract finite container functor, isomorphic to S ⊳ P . Assuming that the costing [F f ] n is defined, then the morphism mapping of F has worst-case lower-bound Proof. The isomorphism to a concrete container explains the extensional behaviour of the abstract container functor; by the isomorphism By the definition of the morphism mapping of concrete container functors S ⊳ P f (s, v) = (s, f • v), it follows that Ff extensionally preserves the size of the incoming container and applies f to every element in a container value. Thus, [F f ] n is bounded below by n[f ] 1 in the worst case.

Remark 3.
The above proposition is stated as a worst-case lower bound. This accounts for functor implementations with a tighter lower bound complexity for some inputs by utilising features of the underlying elements. For example, a compact representation could be provided for lists with shared or repeated elements. A worst case scenario for such an implementation would be a list with no common elements, inducing the linear lower bound.
A lower-bound complexity result is interesting, but not particularly useful for optimisation purposes: given two extensionally equivalent programs f ≡ g if we only know two lower-bounds [f ] n ∈ Ω(t(n)) and [g] n ∈ Ω(t ′ (n)) we cannot determine whether either f g or g f provides any improvement; upper bounds are much more useful. However, in the abstract setting we can only give an upper bound parameterised by representations of additional unknown costs.
Theorem 2 (Abstract container functor cost). Let F be an abstract finite container functor. Its morphism mapping has upper-bound complexity, where for all f : where Q n is a function of the input size n representing the time to access each element in the container (the time to evaluate a valuation). The term R n is a function of n and acts as a catch all : we cannot rule out that possibility of some pathological implementations with redundant expensive computation whose complexity far outweighs the (n[f ] m + nQ n ) term.
If there is no well-defined notion of size for elements A, then m = 1 (see Corollary ??, p. ??).
Proof. This result builds on the lower-bound complexity of F (Proposition 3) which was based on the extensional behaviour of F via its isomorphism to a concrete container. An upper bound must be at least as big as the lower bound, hence the first part of complexity bound here is n[f ] m . In the isomorphic concrete container, accessing an element has constant time but this may not be the case in F. Instead, the cost of accessing each element is accounted for by the term nQ n (since there must be at least n accesses by the earlier argument). Finally, the factor R n provides the opportunity for any additional wasteful computation to dominate the linear-time portion.
Remark 4. The above theorem is quite weak: the R n factor means we do not have a concrete upper bound. However, this still provides a bound that can be used to reason about optimisations. (A similar situation occurs with comonads in the next section). We can analyse two cases: when R n is dominant, and when it is not, and show that an optimisation does not get asymptotically worse even if R n is dominant.

Optimisation 1 (Functor optimisation).
For an abstract or derived container functor F, the axiom Fid ≡ id F can be oriented as an optimising rewrite rule Fid id F .
Proof. Straightforward for either derived or abstract container functors. Let id F (s, v) = (s, v), which in our model has cost:

Remark 5.
One might expect the cost of the identity function to be constant, i.e., (λx.x) ∈ O(1). However, we make complexity claims relative to our cost model of container operations. The model essentially includes an evaluation of a container value at every position (in the current shape), thus identity on a container value is not constant, but requires the resulting container to be evaluated.
The remaining functor axiom F(g • f ) = Fg • Ff is exploited in the classic deforestation transformation Fg • Ff F(g • f ) [26]. Performance is improved by eliminating the intermediate data structure between the two morphism mappings and performing one traversal instead of two. However, this rewrite is not justified via the results here since the complexity of each term is the same; asymptotic bounds cannot distinguish n from 2n. To justify this axiom (in a general setting) requires a more fine-grained (non-asymptotic) costing analysis outside the scope of this paper.

Container comonads
The morphism mapping of the functor construction for containers captures the programming pattern of an element-wise traversal, applying a transformation f : A → B to each element. Comonads generalise this element-wise traversal by allowing a transformation to depend not just on a single element, but on an element and its neighbours. The following compares the signatures of the morphism mapping for a functor (left) with the extension operation of a comonad (right): On the left, the functor applies the morphism f pointwise, where f computes a B value from a single A value. On the right, extension applies the morphism g contextwise, where g computes a B value from possibly multiple A values from the "context" provided by FA. That is, FA is more than just a container functor, it is a container functor with a notion of context, such as a pointer to a particular element. Thus, g can access more than just a single element, and is therefore described as a context-dependent computation (see [18]). For example, the kernel function of a Gaussian blur takes the mean of an element at the current context and its immediate neighbours. FA → FB satisfying the axioms: Thus, the "context" at each element of the list is its suffix, to which extension applies f . The requirement that the lists are non-empty is such that ε is total.
We focus first on the axiom [C3] g † •f † ≡ (g •f † ) † which reassociates extension. The nested use of extension on the right-hand side suggests the possibility of a quadratic difference in complexity compared with the left, which has no such nesting. This is exactly the situation described in the introductory example (Section 1). This kind of nesting can easily arise during program construction and can lead to significant performance issues. Therefore, it is preferable to automatically eliminate this nesting (by a compiler) given a guarantee that it is always an improvement. We infer the complexity difference between the two sides of [C3] from the comonad axioms and show that the Proposition 4 (Shape preservation). The extension operation of a comonad is shape preserving [18,16], similarly to the morphism mapping part of a functor. That is, for an abstract notion of shape given by shape A : FA → F1 defined as the lifting of the terminal morphism shape A = F! A (also used by Jay and Cockett [14]), then for all f : FA → B: shape B • f † = shape A Thus, the comonadic extension of any morphism f : FA → B preserves the shape of the input value in its output.

Corollary 2 (Size preservation).
For a finite container comonad, the extension operation (−) † is size preserving, that is: Since shape A = F! A , by naturality of size A it follows that size 1 • shape A = size A . Combining this with Proposition 4 gives size preservation: The size preservation of comonadic extension is a useful property for deriving complexity bounds on extension and has the further corollary about cost: Corollary 3. For a finite container comonad on F and for any morphisms f : FA → B and g : B → C then the following property holds for the execution time: That is, since f † is size preserving, the output size of f † is the same as the input and therefore the execution time of the composition can be decomposed with [g] parameterised by size n.
Similarly to functors in the last section, both derived and abstract structures are considered in this section. Comonads can be derived from containers with some additional structure.

Directed containers
The subclass of containers known as directed containers has additional structure on shapes and positions which induces a container comonad [3]. The additional structure describes a system of subshapes related to each position in a shape.

Definition 9 (Directed containers).
A container S ⊳ P is called directed if it has the following additional operations for manipulating shapes and positions [3,4]: • o : Π{s : S}. P s -a root position for every shape (the shape parameter s is given implicitly); 1 • ↓: Πs : S. P s → S -computes the subshape at a particular position; • ⊕ : Π{s : S}. Πp : P s.P (s ↓ p) → P s -given a position p ′ in a subshape starting at p, then p ⊕ p ′ gives the position of p ′ within the parent shape.
These satisfy the following two equations on shapes [S1-2] and three positional equations [P1-3]: The implicit parameters of an operation are marked by surrounding them in braces {...}.
The shape parameters have been kept implicit in the above axioms, but [P3], for example, can be rendered with its shape arguments explicit as (p ⊕{s} p ′ ) ⊕{s} p ′′ = p ⊕{s} (p ′ ⊕{s ↓ p} p ′′ ). Modulo the dependent types, rules [P1-3] are essentially that ⊕ and o form a monoid over positions, and [S1-2] that ↓ is a right-monoid action with the set of shapes.
Definition 10 (Derived container comonad). Every directed container (S ⊳ P, ↓, o, ⊕) induces a comonad for the functor S ⊳ P : C → C with counit and extension: The counit operation ε takes the valuation at the root position o. The extension f † on a container value (s, v) produces a container where at each position p the morphism f : FA → B is applied to a sub-container of shape s ↓ p whose valuation takes elements at positions offset by p ⊕{s}.
Example 5 (Directed list container and its comonad). Example 1 (p. 2) defined the list container as N ⊳ Fin where shapes are list lengths. Non-empty lists are a container with N >0 ⊳ Fin and a directed container with s ↓ p = s − p (the subshape is the (length of the) suffix) and o{s} = 0 (the head element) and p ⊕ p ′ = p + p ′ (the global position of p ′ in the subshape starting at p is its position in the entire list).
This produces a non-empty list comonad structure which is extensionally equivalent to the non-empty list comonad given in Example 4.
where m = max p:P s |P (s ↓ p)| is the largest subshape from s for some input (s, v) : S ⊳ P A and Q m is the cost of applying valuations v (parameterised on the maximum subshape size m).
Therefore the cost of applying f † to a container of size n is bounded above by n times the cost of f on input size m (the size of largest possible subcontainer that f is applied to), plus nm times the valuation cost (the cost of retrieving values from the container in each application of f ).
Since positions are of constant size, we treat the cost of ⊕ as constant also in the above proof.

Remark 6.
In the above theorem and proof, the maximum size of the subshapes m is ≥ n by the , the size of the subshape at the current context is equal to the size of the original container (this is exposed in the directed container structure by axiom [S1]). Therefore, as m is the size of the largest subshape, and the subshape at the root position is the size of n, it follows that m ≥ n.
The above theorem provides the basis to show that [C3] can be oriented as an optimisation:

Optimisation 2. For a derived container comonad, axiom [C3] can be oriented as (g•f † ) †
g † •f † guaranteeing an asymptotic improvement or not making the complexity worse.
Proof. Assume input (s, v). Let n = |P s|, m 1 = max p:P s |P (s ↓ p)| (largest subshape size) and m 2 = max p:P s,p ′ :P (s↓p) |P ((s ↓ p) ↓ p ′ )| (largest sub-subshape size). By Theorem 3 and Corollary 2: Each bound shares a common summand n[g] m1 +nm 1 Q m1 and a common factor n on the remaining terms. It therefore remains to prove ( This follows from the property that m 1 = m 2 , that is the maximum subshape size is equal to the maximum sub-subshape size, which follows from the directed container axioms: 1. First, consider a general property on functions: let X and Y be sets and a : X → Y and b : Y → Z be functions where X and Y are finite and a is surjective. Then: {b(y) : ∀y ∈ Y } = {b(a(x)) : ∀x ∈ X} since a maps to its entire co-domain Y .

From axiom [P1]
, it follows that ⊕{s} is surjective, mapping to all values in its co-domain P s. That is, ∀p ∈ (P s) ∃p 1 ∈ (P s) and ∃p 2 ∈ (P (s ↓ p)) such that p 1 ⊕{s} p 2 = p, where p 1 = p and p 2 = o by axiom [P1] p ⊕ o = p.
3. Instantiate the general property (1) with X = Πp : P s.P (s ↓ p) (which is finite due to our restriction to finite containers and hence finite sets of positions), Y = P s (also finite) and Z = N, with a = ⊕s (which is surjective (2)) and b p = |P (s ↓ p)| (the cardinality of positions at subshape s from position p). Thus: {|P (s ↓ p)| : ∀p ∈ P s} = {|P (s ↓ (p 1 ⊕ {s}p 2 )| : ∀p 1 ∈ P s, ∀p 2 ∈ P (s ↓ p)} which are both finite sets by the finiteness of X and Y .

Abstract container comonads
For abstract (non-derived) container comonads, this section shows their complexity is similar to those of the derived container comonads. But how do we characterise abstract container comonads in order to have enough structure to derive the complexity bounds?
The above definition is essentially that of an abstract finite container functor (Definition 7) but where F is a comonad. This is all that is needed to derive the following complexity bound; an isomorphic directed container structure on S ⊳ P (and thus a comonad on S ⊳ P ) is not required, just that there is an isomorphic container S ⊳ P .
Theorem 4 (Abstract container comonad cost). Let F be an abstract finite container with comonad structure (F, ε, (−) † ) and a container S ⊳P such that α : F ∼ = S ⊳ P . The size of abstract container values is given by size ′ A : FA → N = size A • α A . The extension (−) † then has the upper bound (assuming the cost [f † ] n is indeed defined): with m ≥ n where m represents the size of the largest subcontainer passed to f , Q n represents costs for constructing subcontainers and valuation cost, and R n covers pathological implementations.
Proof. The proof resembles that of Theorem 2 for abstract functors with the parameterisation of the complexity bound by Q n and R n . In contrast with the complexity bounds of abstract container functors (Proposition 3, p. 6), this result relies more on the general axioms of the structure on F.

By shape preservation of comonads (Proposition 4)
, the size of a container computed by the extension f † is the size of the input container, i.e., size ′ where the unlabelled commuting squares are naturality properties: By the comonad axiom [C2] ε N • size ′ A † = size ′ A , the size of the subcontainer computed for the current context is the size of the input container (Remark 6, p. 10). Thus the maximum size m of a subcontainer computed by extension (−) † is at least n, i.e. m ≥ n.

The comonad axiom [C1] ε †
A = id FA implies that for some input container x : F A and function f : F A → B then f † x : F B applies f to n subcontainers of x where each subcontainer has each element of x at its current context. This follows by a contradiction: assume that f † does not apply f to n subcontainers. Therefore, to compute an output container of size n (from point (1) above) of element type B, the operation f † must copy some of the results from applying f . Therefore, applying ε † A to an input container with distinct elements A would produce an output container with duplicate elements. Therefore, ε † A = id FA and the initial assumption must be false. Combined with point (2), the execution time is therefore bounded above by n[f ] m where m is the maximum size of subcontainers, plus nmQ n to account for constructing subcontainers for each application of f and accessing elements (the valuation cost).
4. Finally, the term R n accounts for arbitrary wasteful implementations.

Optimisation 3.
For any container comonad, axiom [C3] can be oriented as (g • f † ) † g † • f † guaranteeing an asymptotic improvement or not making the complexity worse (in the case where Q n or R n dominate all other terms).
Proof. Essentially the same as the proof for Optimisation 2 (p. 11), modulo the additional term R n which appears in both complexity terms, but with an additional linear factor for the (more

Optimisations for counit
The computational cost of the counit operation ε A : FA → A has yet to be considered. Its axioms [C1] ε † ≡ id and [C2] ε • f † ≡ f are clearly amenable to orientation as optimising rewrites. This is formalised briefly here, using similar approaches to the above.  ) where m is the size of the maximum subshape (Theorem 3, p. 10). By [S1] it follows that m ≥ n since the current shape is a subshape of itself (at the root position) and therefore the maximum subshape size must be at least n.

Summary
The results of this section showed complexity bounds for derived and abstract container comonads, providing the proof that each of the comonad axioms can be oriented to give an optimisation. The rewrite (g • f † ) † (g † • f † ) (Optimisation 2 and 3) corresponds to the flattening transformation discussed in the introduction (Section 1), moving from a quadratic to linear program. We revisit this example concretely using the formalism of this paper.
Example 6 (Arrays). The introductory example of this paper showed two imperative array programs with the same extensional behaviour, but differing asymptotic behaviours: where A, B, and C are one-dimensional arrays and f and g are pure functions. This example can be explained in the framework of this paper because the above traversals are instances of the extension operation of an array comonad [17] which has a cursor -a position which identifies or focusses on a particular element in the container (see [4, §4.5]). In the above loops, the iteration variables i and u act as cursors to the arrays. For example, in the left program, u is used as the cursor for A and i is used as the cursor for B. One-dimensional arrays with a cursor are captured by the container: The Array container also has the following directed structure: o{(l, c)} = c the root position is marked by the cursor (l, c) ↓ p = (l, p) the subshape at position p has same length, but with cursor p p ⊕ p ′ = p ′ the offset of position p to p ′ is just the cursor p ′ Inlining the above definitions, the derived extension operation of the comonad for this directed container is defined as: That is, extension (−) † produces an array with the same size and cursor as the input array, where elements of the new array are computed by applying f to arrays ((l, p), v) which are the original array "refocussed" at each position p by setting p as the cursor. This produces the following behaviour captured by the imperative code: where A corresponds to the valuation and p to the position which is used as the cursor within the array (of length l). The output array B corresponds to the new valuation. Thus, the two array programs in the introductory example correspond to two comonadic array container computations: (g • f † ) † and g † • f † respectively. 3 Applying Theorem 3 on the derived container comonad cost and Corollary 2 on the cost of post-composing functions after comonadic extension, we recover the complexity results shown in the introduction (with some additional precision). By the above definition of the array comonad, the size of largest subshape m = max p:P s |P (s ↓ p)| = |P s| = n since subshaping ↓ just changes the position of a cursor rather than changing the size of an array. Thus, assuming that the valuation cost is constant Q n = 1, we derive the complexity bounds: On the right, the extension traversals are sequentially composed, whereas on the left the extension of f is nested within the traversal for the extension of g, leading to the quadratic factor on the cost of f. Applying Optimisation 3 (p. 13) to this program rewrites (g • f † ) † to g † • f † , thus reducing the asymptotic complexity. The nested array-traversal optimisation discussed in the introduction is therefore an example of structuring a program via a comonad, and optimising by applying the [C3] axiom as a rewrite rule.
In a programming, the kind of nesting seen in this example is more likely to occur when composing library functions than when writing straight-line imperative code by hand. Indeed, existing comonadic libraries sometimes suffer from complexity problems due to overly nested operators, for example in the comonadic notation for Haskell which dualises do-notation [18] and as noted in the work of Foner on fixed-points over comonadic extension and comonadic arrays [7]. By adding a [C3] rewrite rule into the compiler, this situation can be remedied.

Related work
Related to the notion of containers (à la Abbott et al.) are shapely datatypes [12,14], which were shown to be a subset of containers with finite cardinals [1]. Shapely data types have a shape morphism sh : F X → F 1 (which can be given by F ! A ) and data morphism data : F X → [X], mapping to a list of values. A shapely data type (or functor) is then the pullback of these two morphisms. Size can be determined from the data morphism using the length of the list, e.g., size = length • data. Thus, our results for abstract constructions can also be applied also to shapely functors.
Various works have provided a cost analysis for algorithmic skeletons in the context of parallel programming e.g. [15,10,22,13], which have similar motivation to this work: reasoning about performance abstractly to inform program optimisation. Riely and Prins prove that the flattening transformation (for nested parallelism) provides an improvement in their costing [20]. Size inference for concrete list data structures has been used for optimising compilation [13].
The work of Hayashi leverages a concrete cost-model for parallel architectures (accounting for memory model, communication cost) in the context of a vector-based set of algorithmic skeletons [10]. There are many possible cost models available (Hayashi's thesis gives a survey of cost models and different cost analysis techniques [10]). However, many of these costings are specialised to particular data structures, e.g., lists or vectors, and focus more on the distribution and communication cost, rather than a general computational complexity. The analysis here is much more abstract, further divorced from any implementation (sequential or parallel). More abstract approaches have costed parallel programs over shapely functors [12]. The work of Jay considers map, fold, and zip constructs in the more abstract setting [13]. The present paper broadens this to the larger class of containers and studied the comonad pattern, which has not been given a complexity analysis in the literature.
Skillicorn and Cai provide a cost-model for Bird-Meertens algebra-of-programming style [22]. They define the (sequential) cost of the map operation on lists as t(map f ) n = nt(f ) n , and of concat (i.e., multiplication of the list monad) as t(concat) n = n − 1. Thus, the associative axiom of the list monad t(concat • (map concat)) n = n(n − 1) + n − 1 = n 2 − 1 versus t(concat • concat) n = n − 1 + n − 1 = 2(n − 1). This justifies the reassociating transformation of a list monad. Again, a particular data structure is studied.

Discussion and conclusion
Infinite containers. We have considered models of finite, discrete containers in this paper, since our asymptotic bounds are defined for finite cardinals. Can similar reasoning principles be used to explain asymptotic behaviour of infinite containers? One potential avenue for infinite but discrete containers is to define bounds in terms of ordinals.
Example 2 defined the infinite stream container { * } ⊳ (const N). The asymptotic behaviour might be reasoned about via ordinals, where ω (linear, infinite) and ω 2 (quadratic, infinite) can be distinguished. This provides a way to explain the overhead incurred when nesting the extension operation for stream comonads.
Category theory models of complexity. Various works seek to give a category theoretic account of complexity classes, algorithms, and wastefulness (inefficiency), e.g., [27,5]. An interesting future direction would be to consider containers on the category of timed sets, giving a measure of the execution time of morphisms and complexity measures from within the model, rather than externally via a cost function as done here. This is further work.
Relatedly, it may be possible that the account of execution cost and complexity for comonads here (and in the future for monads) could be reduced into more basic structures such as adjunctions, and studied from a categorical perspective. This is further work.
Concluding remarks. The analysis in the paper provides a foundation for program optimisation for programs structured in terms of containers, functors, and comonads. These are already common programming idioms in functional programming. When combined with user-defined rewrite rules, this provides a powerful technique for suggesting optimisations. Furthermore, by instantiating the execution time function [−] n the parameterised cost model [−] provides a way to specialise the results to particular precise, concrete abstract machine models.
It should be pointed out that, whilst these optimisations are proven to not make a program asymptotically worse, this is still a fairly weak statement. It may be the case that such rewrites interact poorly with other transformations in a compiler, leading to worse performance. As always with optimising compilation, care must be taken.
Containers are a useful syntax and abstraction for exploring generic programming interfaces for common data types. There are still plenty of avenues for further exploration. This paper demonstrated that container abstractions can be used to reason about efficiency, which has not appeared before, and for which there is much more work to be done.