The categorical scoop on attributed C-sets

c-sets
attributed-c-sets
In this post we unveil the category of attributed C-sets. It is an upgrade to the category of C-sets that makes each element of a component set (e.g. each vertex or edge in a graph) have attributes, which may be elements of arbitrary sets/Julia types. In order to formalize attributed C-sets, we also take a tour of double category theory and profunctors.
Author

Owen Lynch

Published

October 5, 2020

The trouble with C-sets is that they cannot model data structures such as labelled graphs, which are graphs where every edge and vertex has an associated element from some set of labels. In this blog post, we will discuss an extension to C-sets which we call attributed C-sets that solves this issues. This formalism is mainly inspired by the viewpoint of the “Algebraic Databases” paper (), though in truth we take more after the “databases” part than we do the “algebraic” part.

The double category of profunctors

It is possible to develop attributed C-sets within the 2-category of categories, functors, and natural transformations. However, we gain much in simplicity when we move to a double categorical framework, and it is for this reason that we dive into a bit of abstraction before moving to attributed C-sets.

From a graphical perspective, a double category is a mathematical object where the basic unit of analysis is a square that looks like this:

Although this looks like a commutative diagram, it is in general not. Rather, it is a depiction of a 2-cell, and the morphisms and objects are merely the boundaries of the 2-cell. In an actual diagram, the asterisks and arrows would all be named, but we have left out names for now to emphasize the shape. These squares can be composed both vertically:

and horizontally

A double category can be very succinctly defined as a category in Cat\mathsf{Cat}. This is analogous to the definition of a Lie group as a “group in the category of smooth manifolds.” However, it is a little more subtle than that for a couple reasons, so we will spell it out explicitly. A double category D\mathbb{D} is defined by

  1. A category D0\mathbb{D}_0. We say that an object in this category is an object in D\mathbb{D} (i.e., the corners of a square). A morphism in this category is a vertical morphism in D\mathbb{D} (i.e., the left and right sides of a square).
  2. A category D1\mathbb{D}_{1}. An object in this category is a horizontal morphism in D\mathbb{D} (i.e., the top and bottom of the square), and a morphism is a 2-cell, i.e. an entire square. This will make more sense when we introduce the next element. Vertical composition of 2-cells is given by regular composition in D1\mathbb{D}_{1} (so in particular, it is strictly associative and unital).
  3. Two functors dom,codom:D1D0\operatorname{dom}, \operatorname{codom}: \mathbb{D}_{1} \to \mathbb{D}_{0}. On an object pp of D1\mathbb{D}_{1} (i.e. a horizontal morphisms), dom\operatorname{dom} and codom\operatorname{codom} give the objects of D0\mathbb{D}_{0} (i.e. objects of D\mathbb{D}) that are the domain and codomain of pp. On a morphisms α\alpha of D1\mathbb{D}_{1} (i.e. a 2-cell), dom\operatorname{dom} and codom\operatorname{codom} give the morphisms of D0\mathbb{D}_{0} (i.e. vertical morphisms) that are on the left and right of α\alpha.
  4. The heart of a double category is horizontal composition. Intuitively, horizontal composition is a functor from the category of “pairs of composable horizontal morphisms” to D1\mathbb{D}_{1}. This category is the equalizer of codomπ1\operatorname{codom}\circ \pi_{1} and domπ2\operatorname{dom}\circ \pi_{2} which both go from D1×D1\mathbb{D}_{1} \times \mathbb{D}_1 to D0\mathbb{D}_{0}. We write it as an equalizer rather than a pullback to emphasize that it is a sub-category of D1×D1\mathbb{D}_{1} \times \mathbb{D}_{1}. In any case, we have a functor \odot from this equalizer to D1\mathbb{D}_{1}, and this functor on objects is horizontal composition of horizontal morphisms, and on morphisms is horizontal composition of 2-cells.
  5. Finally, we have a functor uu from D0\mathbb{D}_{0} to D1\mathbb{D}_{1} that is a unit for \odot, again up to unique natural isomorphism. This gives the identity for horizontal composition of both horizontal morphisms (when applied to objects of D0\mathbb{D}_{0}), and 2-cells (when applied to morphisms of D0\mathbb{D}_{0}).

Before we get into profunctors, let’s talk about some simple examples of double categories.

Example 1. Given any category C\mathsf{C}, there is a double category where the 2-cells are commutative squares in C\mathsf{C}. The category D1\mathbb{D}_{1} is actually a familiar category: the arrow category of C\mathsf{C}. This is a strict double category, in that the natural isomorphisms for associativity and unitality of CC are actually the identity.

Example 2. Given any 2-category C\mathsf{C}, there is a double category where the 2-cells are squares that commute up to 2-isomorphism. The reader is invited to investigate this double category further.

Example 3. Given any category C\mathsf{C} with pushouts, there is a double category where the horizontal morphisms are cospans in C\mathsf{C}. A 2-cell is a commutative diagram in C\mathsf{C} that looks like this:

and horizontal composition is by pushout. John Baez and Kenny Courser make a very compelling argument that this construction is key to understanding open systems, and we encourage the interested reader to look at the work that they have done in this area (; ).

The double category that we are interested in is called Prof\mathbb{P}\mathsf{rof}, and its generic 2-cell is illustrated in the following diagram.

A\mathsf{A}, B\mathsf{B}, X\mathsf{X}, and Y\mathsf{Y} are all categories. FF and GG are functors AX\mathsf{A} \to \mathsf{X} and BY\mathsf{B} \to \mathsf{Y} respectively. MM and PP are functors Aop×BSet\mathsf{A}^{\mathrm{op}} \times \mathsf{B} \to \mathsf{Set} and Xop×YSet\mathsf{X}^{\mathrm{op}} \times \mathsf{Y} \to \mathsf{Set} (also known as profunctors). And finally, α\alpha is a natural transformation from MM to P(F×G)P \circ (F \times G). That is, the components of α\alpha have the form αA,B:M(A,B)P(F(A),G(B))\alpha_{A,B} : M(A,B) \to P(F(A),G(B)).

What we have said so far should be sufficient for the reader to implement the first three parts of a double category, and we encourage the reader to do so before continuing.

At this point, we have not defined composition or units in Prof\mathbb{P}\mathsf{rof}. However, we have enough material to define attributed C-sets at this point, so in order to give the reader a break from abstract nonsense, we will talk about them now.

What is an attributed C-set?

If we think about C-sets as databases, their “schemas” are finitely presented categories. Before we talk about attributed C-sets, we must talk about the construct that plays an analogous role. The “schema” for an attributed C-set is a horizontal morphism Attr\operatorname{Attr} in Prof\mathbb{P}\mathsf{rof} whose source is a finitely presented category C\mathsf{C}, and whose target is a finitely presented discrete category Data\mathsf{Data}. We visualize this in the following way:

Attributed C-set schema Visualization

Let’s parse this diagram step-by-step. On the left in yellow, we have C\mathsf{C}, and this is a standard way of picturing a finitely-presented category. On the right in blue, we have a discrete category (no arrows). Finally, connecting C\mathsf{C} and Data\mathsf{Data}, we have two green ovals. These ovals represent the sets Attr(E,X)\operatorname{Attr}(E,X) and Attr(V,X)\operatorname{Attr}(V,X). We picture an element of Attr(E,X)\operatorname{Attr}(E,X) as an arrow from EE to XX, which is consistent with the viewpoint that a profunctor is a heterogenous Hom-functor. We call an element of Attr(E,X)\operatorname{Attr}(E,X) an attribute.

The reader is likely unfamiliar with the notation src;vdec\operatorname{src}; \operatorname{vdec} because to our knowledge, it is non-standard. A more standard way of writing it would be Attr(src,idX)(vdec)\operatorname{Attr}(\operatorname{src},\operatorname{id}_{X})(\operatorname{vdec}). Namely, it is the action of the morphism srcHomC\operatorname{src}\in \operatorname{Hom}_{\mathsf{C}} on vdecAttr(V,X)\operatorname{vdec}\in \operatorname{Attr}(V, X). We write it like src;vdec\operatorname{src}; \operatorname{vdec} to suggest that we are precomposing src\operatorname{src} with vdec\operatorname{vdec}.

As always, when we are doing computational category theory we must introduce presentations whenever we introduce new mathematical object. In this case, to present a “schema”, we present a category C\mathsf{C} and a discrete category Data\mathsf{Data} in a standard way, but we also must present a profunctor, and we expect that the reader is not so familiar with this.

Fortunately, it is not so difficult; to present a profunctor we simply give generating elements in each of the component sets (i.e., edecAttr(E,X)\operatorname{edec}\in \operatorname{Attr}(E,X)), and then we write down some equalities involving precomposition (for instance, if we wanted to say that the decoration on an edge was equal to the decoration on its source, we could write src;vdec=edec\operatorname{src}; \operatorname{vdec}= \operatorname{edec}).

In Catlab, we would write down the presentation of decorated graphs like so:

@present SchDecGraph(FreeSchema) begin
  V::Ob
  E::Ob
  src::Hom(E,V)
  tgt::Hom(E,V)
  
  X::AttrType
  edec::Attr(E,X)
  vdec::Attr(V,X)
end

Note that this presention has no equations. If we did the category of decorated symmetric graphs, we would end up with

@present SchDecSymmetricGraph <: SchDecGraph begin
  inv::Hom(E,E)

  compose(inv,inv) == id(E)
  compose(inv,src) == tgt
  compose(inv,tgt) == src

  compose(inv,edec) == edec
end

The last equation says that the decoration on an edge should be the same as the decoration on its inverse.

It is perhaps best to understand attributed C-sets by looking at how we implement them in Catlab. Each object in C\mathsf{C} becomes a table. The table for cCc \in \mathsf{C} has a column of type Int for each generating morphism with domain cc, and a column of some other type for each generating attribute with domain cc. We will later see exactly how this type is determined.

The columns corresponding to morphisms in C\mathsf{C} are similar to FOREIGN KEY columns in SQL: they point to rows in other tables. The columns corresponding to attributes are just normal columns, but they might have some constraints corresponding to the equations in the presentation.

Formally, we define an attributed C-set on the schema (C,Data,Attr)(\mathsf{C},\mathsf{Data},\operatorname{Attr}) to be a 2-cell in Prof\mathbb{P}\mathsf{rof} that looks like this:

Let’s pick apart this definition. First of all, we have an ordinary C-set FF. This is the “core” of an attributed C-set, and we should understand this fairly well.

Then we have KK, which gives the attributes types. In this presentation, we have chosen to make Set\mathsf{Set} the codomain of KK. However, in Catlab, the codomain of KK is really the category of Julia types. This is not so well-behaved, but that’s the difference between math and computing for you. Typically, we actually treat KK as part of the schema. This means that we would be working with R\mathbb{R}-decorated graphs, or Z\mathbb{Z}-decorated graphs, instead of just graphs decorated by an unspecified type.

Finally, we have α\alpha, which sends an attribute xAttr(c,X)x \in \operatorname{Attr}(c,X) to a column of the data table for cc, of type K(X)K(X). The profunctor Arr\operatorname{Arr} is defined in exactly such a way to make this work: Arr([m],K(X))\operatorname{Arr}([m],K(X)) is the set of length-mm arrays with element type K(X)K(X), where [m][m] denotes the set {1,,m}\{1,\ldots,m\}.

Another way of thinking about this is that we can treat FF as a 2-cell from the profunctor HomC\operatorname{Hom}_{\mathsf{C}} to the profunctor HomFinSet\operatorname{Hom}_{\mathsf{FinSet}}. In this guise, FF sends f:cdf : c \to d to an element F(f)HomFinSet(F(c),F(d))F(f) \in \operatorname{Hom}_{\mathsf{FinSet}}(F(c),F(d)) that represents the column in the data table for cc. When thought about this way, it becomes more clear how FF and α\alpha are analogous even though they seem like they are different shapes.

Back to Prof\mathbb{P}\mathsf{rof}

Note: for the reader with categorical inclinations, this section will be very interesting. For the reader who is mainly interested in attributed C-sets, we suggest skimming this section and then moving on to the next.

We now know what an attributed C-set is, but in category theory, that’s only half the battle! We must also define the morphisms!

To do this, we need to further investigate Prof\mathbb{P}\mathsf{rof}. One good way of thinking about profunctors is that they are kind of like matrices (). That is, if we work with sets instead of categories, a map C×DRC \times D \to \mathbb{R} is a matrix. This analogy goes deeper than one might expect.

In the context of matrices, the following 5 things are all equivalent.

  1. A map C×DRC \times D \to \mathbb{R}
  2. A map CRDC \to \mathbb{R}^{D}
  3. A map DRCD \to \mathbb{R}^{C}
  4. A linear map RCRD\mathbb{R}^{C} \to \mathbb{R}^{D}.
  5. A linear map RDRC\mathbb{R}^{D} \to \mathbb{R}^{C}.

It turns out that this is directly analogous to profunctors. A profunctor P:CDP : \mathsf{C} ⇸\mathsf{D} has the following equivalent forms.

  1. A functor P:Cop×DSetP : \mathsf{C}^{\mathrm{op}} \times \mathsf{D} \to \mathsf{Set}.
  2. A functor CopSetD\mathsf{C}^{\mathrm{op}} \to \mathsf{Set}^{\mathsf{D}}.
  3. A functor DSetCop\mathsf{D} \to \mathsf{Set}^{\mathsf{C}^{\mathrm{op}}}.
  4. A colimit-preserving functor ΛP:SetCSetD\Lambda_{P} : \mathsf{Set}^{\mathsf{C}} \to \mathsf{Set}^{\mathsf{D}}.
  5. A colimit-preserving functor ΛP:SetDopSetCop\Lambda_{P}' : \mathsf{Set}^{D^{\mathrm{op}}} \to \mathsf{Set}^{\mathsf{C}^{\mathrm{op}}}. This is for the same reasons as above.

The equivalence of 1, 2, and 3 are provided by the product-hom adjunction in Cat\mathsf{Cat}. However, understanding 4 and 5 takes some work. We explain this by analogy, rather than by rigorous proof. The analogy proceeds in several steps.

  1. Just as the canonical basis vectors of RC\mathbb{R}^{C} are in canonical bijection with CC, the representable functors (i.e. functors of the form Hom(c,):CSet\operatorname{Hom}(c,-) : \mathsf{C} \to \mathsf{Set} for cCc \in \mathsf{C}) are in canonical isomorphism with Cop\mathsf{C}^{\mathrm{op}} (by the map cHom(c,)c \mapsto \operatorname{Hom}(c,-), of course!). This is important, because in the next step we will see that the representable functors form a basis of sorts for SetC\mathsf{Set}^{\mathsf{C}}.
  2. Just as any element of RC\mathbb{R}^{C} can be written canonically as a linear combination of basis vectors, any element of SetC\mathsf{Set}^{\mathsf{C}} can be written canonically as a colimit of representable functors.
  3. Therefore, just a linear map is uniquely determined by its action on a basis, which is the same thing as a map CRDC \to \mathbb{R}^{D}, a colimit-preserving functor is uniquely determined by its action on the representable functors, which is the same thing as a map CopSetD\mathsf{C}^{\mathrm{op}} \to \mathsf{Set}^{\mathsf{D}}.

To go beyond the presentation in terms of analogy, we encourage the reader to consult a standard text on category theory, such as (), and look up the Yoneda lemma and the writing of a presheaf as a canonical colimit of representables.

The fourth equivalent form is the most useful form for defining the composition of P:CDP : \mathsf{C} ⇸\mathsf{D} and Q:DEQ : \mathsf{D} ⇸\mathsf{E}. That is, simply compose ΛP\Lambda_{P} and ΛQ\Lambda_{Q}! This works because colimits are preserved across the composition. Additionally, it is clear what the identity is: 1SetC1_{\mathsf{Set}^{\mathsf{C}}}!

However, it is also useful to have an explicit formula for composition and identity in terms of profunctors as functors Cop×DSet\mathsf{C}^{\mathrm{op}} \times \mathsf{D} \to \mathsf{Set}. This is provided by a funny little colimit called a coend, that looks like this:

(PQ)(c,e)=dDP(c,d)×Q(d,e) (P \odot Q)(c,e) = \int^{d \in \mathsf{D}} P(c,d) \times Q(d,e)

Essentially, dDP(c,d)×Q(d,e)\int^{d \in \mathsf{D}} P(c,d) \times Q(d,e) is the coproduct dDP(c,d)×Q(d,e)\coprod_{d \in \mathsf{D}} P(c,d) \times Q(d,e) modulo the equivalence relation that for any xP(a,d1)x \in P(a,d_{1}), f:d1d2f : d_{1} \to d_{2}, and yQ(d2,c)y \in Q(d_{2},c), (x;f,y)(x,f;y).(x;f,y) \sim (x,f;y).

This gives a rather neat explanation of the identity

cCHomC(c,c)×P(c,d)P(c,d) \int^{c' \in \mathsf{C}} \operatorname{Hom}_{\mathsf{C}}(c,c') \times P(c',d) \cong P(c,d)

Namely, to make the function \to, we map (f,x)(f,x) to f;xf;x, and to make the function \leftarrow, we map xx to (1c,x)(1_c,x). The reader can verify that this defines a bijection.

The reader is encouraged to verify that taking the identity on SetC\mathsf{Set}^{\mathsf{C}} and contorting it into a map Cop×CSet\mathsf{C}^{\mathrm{op}} \times \mathsf{C} \to \mathsf{Set} gives precisely HomC\operatorname{Hom}_{\mathsf{C}}.

Now, this gives us horizontal composition of horizontal arrows. We still need horizontal composition of 2-cells. However, the seeds of this composition are already sewn in the definition of horizontal composition as a colimit. If α:MP\alpha : M \to P and β:NQ\beta : N \to Q are 2-cells, as depicted in the diagram

then we can use the universal property of the coend to define

(αβ)(c,e)=dDα(c,d)×β(d,e) (\alpha \odot \beta)(c,e) = \int^{d \in \mathsf{D}} \alpha(c,d) \times \beta(d,e)

That is, for each dd we have a map α(c,d)×β(d,e):M(c,d)×N(d,e)P(F(c),G(d))×Q(G(d),H(e))\alpha(c,d) \times \beta(d,e) : M(c,d) \times N(d,e) \to P(F(c),G(d)) \times Q(G(d),H(e)), and each element of (MN)(c,e)(M \odot N)(c,e) is represented by at least one (x,y)M(c,d)×N(d,e)(x,y) \in M(c,d) \times N(d,e) for some dd, so we just apply α(c,d)×β(d,e)\alpha(c,d) \times \beta(d,e) to that representative, and we get a representative of something in (PQ)(F(c),H(e))(P \odot Q)(F(c),H(e)).

With this, we have a full definition of the double category Prof\mathbb{P}\mathsf{rof}. Before we get back to attributed C-sets, we will quickly discuss one interestesting feature of this category.

The question is, what is a 2-cell with a frame that looks like this:

Given that the top and bottom are the identity for horizontal composition, one might suggestively write this as

This implies that α\alpha might be a natural transformation between FF and GG. Let’s see if that holds up. Define αˉc=α(c,c)(1c)HomD(F(c),G(c))\bar{\alpha}_{c} = \alpha(c,c)(1_{c}) \in \operatorname{Hom}_{\mathsf{D}}(F(c),G(c)). Suppose that f:c1c2f : c_{1} \to c_{2} in CC. Then by naturality of α\alpha,

f;α(c2,c2)(1c2)=α(c1,c2)(f)=α(c1,c1)(1c1);f.f;\alpha(c_{2},c_{2})(1_{c_{2}}) = \alpha(c_{1},c_{2})(f) = \alpha(c_{1},c_{1})(1_{c_{1}});f.

However, this is precisely the naturality condition for αˉ\bar{\alpha} in disguise, as f;α(c2,c2)(1c2)=F(f);αˉc2f;\alpha(c_{2},c_{2})(1_{c_{2}}) = F(f) ; \bar{\alpha}_{c_{2}}, and α(c1,c1)(1c1)=αˉc1;G(f)\alpha(c_{1},c_{1})(1_{c_{1}}) = \bar{\alpha}_{c_{1}} ; G(f).

Moreover, if we have a natural transformation β:FG\beta : F \Rightarrow G, we can define β~\tilde{\beta} by β~(c1,c2)(f)=F(f);βc2=βc1;G(f).\tilde{\beta}(c_{1},c_{2})(f) = F(f) ; \beta_{c_{2}} = \beta_{c_{1}} ; G(f).

Therefore, the 2-category Cat\mathsf{Cat} is hiding in Prof\mathbb{P}\mathsf{rof}!

We now have all the technology we need to finish the definition of attributed C-sets.

The category of attributed C-sets on a schema

A morphism of C-sets is fairly easy to define: it is just a natural transformation γ\gamma between two functors F,G:CSetF,G : \mathsf{C} \to \mathsf{Set}. If (F,α)(F,\alpha) and (G,β)(G,\beta) are attributed C-sets on the schema (C,Data,Attr,K)(\mathsf{C},\mathsf{Data},\operatorname{Attr},K), one natural definition for morphisms between them is γ:FG\gamma : F \Rightarrow G such that γ\gamma preserves attributes. This is most clearly seen if we introduce some notation. If aF(c)a \in F(c) and xAttr(c,X)x \in \operatorname{Attr}(c,X), we define a.xa.x to be α(c,X)(x)(a)\alpha_{(c,X)}(x)(a). That is, a.xa.x is the element of K(X)K(X) that α\alpha assigns to aa. The reason for this notation is that it reminds us of dot-syntax in programming languages, where swiss.cheese returns the value of the field cheese on the object swiss. Viewing aa as a row in a database, a.xa.x is the value of column xx at row aa.

In any case, if γ:FG\gamma : F \to G is a natural transformation, we say that it preserves attributes if γc(a).x=a.x\gamma_{c}(a).x = a.x for all cCc \in \mathsf{C}, XDataX \in \mathsf{Data}, aF(c)a \in F(c), and xAttr(A,X)x \in \operatorname{Attr}(A,X).

For the reader who was paying attention in the last section, it turns out that this condition can be very succinctly stated categorically as α=γ~β\alpha = \tilde{\gamma} \odot \beta, or in commutative diagram form as

To verify this, one simply needs to chug through the definition of γ~\tilde{\gamma} and the definition of \odot. This defines the category of attributed C-sets over a schema as a sort of double category-theoretic analogue of a slice category.

The only thing remaining is to name this category! We have chosen to name it AttrK-C-Set\operatorname{Attr}^{K}\text{-}\mathsf{C}\text{-}\mathsf{Set}, which can be shortened to Attr-Set\operatorname{Attr}\text{-}\mathsf{Set} if C\mathsf{C} and KK are clear from the context.

However, this is not the only possible definition for a category with attributed C-sets as objects; there are other types of morphisms that one might want to consider. For instance, we could let a morphism be a commutative diagram of the form

Here δ\delta would be a natural transformation between J,K:DataSetJ,K : \mathsf{Data} \to \mathsf{Set}, so δ~\tilde{\delta} would be an analogous 2-cell to γ~\tilde{\gamma}. In this type of morphism, attributes are allowed to change, but they need to be related by a natural transformation. We are less likely to use this kind of morphism in Catlab for the simple reason that Julia functions don’t serialize, and Julia functions are the homs in our implementation of Set\mathsf{Set}. However, it is an equally valid definition. A special case of this would be J=KJ = K.

Both of these categories are instances of a more general construction which takes slice categories into the double-category realm. The properties and full definition of this more general construction have yet to be determined, but we will likely cover this in a future blog post.

Variations and extensions

A simple variation on attributed C-sets would be to replace FinSet\mathsf{FinSet} with the category of finite sets and partial maps, or replace Arr\operatorname{Arr} with the profunctor that takes ([m],S)([m],S) to the set of partial maps from [m][m] to SS. The first variation is actually in Catlab, because it turned out to be convenient.

However, there are also many other possiblilities. For instance, we could allow CC to have finite products, and force FF to be product-preserving, or replace FinSet\mathsf{FinSet} with FinRel\mathsf{FinRel}. One open problem is how to specify that some attributes or morphisms should end up being injective. It is clear that we have just scratched the surface in terms of things that fit this pattern, and we are excited to see what the future has in store for attributed things!

References

Baez, John C., and Kenny Courser. 2019. “Structured Cospans.”
Courser, Kenny. 2020. “Open Systems: A Double Categorical Perspective.”
Riehl, Emily. 2016. Category Theory in Context. Mineola, NY: Dover Publications. http://www.math.jhu.edu/~eriehl/context.pdf.
Schultz, Patrick, David I. Spivak, Christina Vasilakopoulou, and Ryan Wisnesky. 2016. “Algebraic Databases.”

Footnotes

  1. One way of thinking about this is by analogy to group actions on a set. If C\mathsf{C} and D\mathsf{D} are single-element categories with all invertible morphisms (i.e. groups), then a profunctor CD\mathsf{C} ⇸\mathsf{D} is precisely a set with a left action of C\mathsf{C} and a right action of D\mathsf{D}.↩︎

  2. In an actual implementation, we do not store the column for all of the attributes, just the generating attributes. This saves space.↩︎