The module Catlab.Programs provides domain-specific languages (DSLs) for constructing diagrams of various kinds. The DSLs, implemented as Julia macros, are based on the syntax of the Julia language but often interpret that syntax very differently from standard Julia programs. Conversely, this module offers preliminary support for generating Julia code from wiring diagrams.

There are two major macros for constructing wiring diagrams:

  • @program, for directed wiring diagrams (DWDs)
  • @relation, for undirected wiring diagrams (UWDs)

In addition, there is a family of related macros for constructing category-theoretic diagrams:

  • @graph, for constructing a graph
  • @fincat, for presenting a category as a graph together with path equations
  • @finfunctor, for defining a functor between two finitely presented categories
  • @diagram and @free_diagram, for defining a diagram in a category

Generalizing the last two macros, the modules provides DSLs for functorial data migration:

  • @migrate, for migrating data between acsets
  • @migration, for defining data migrations between schemas



Evaluate a morphism as a function.

If the morphism will be evaluated only once (possibly with vectorized inputs), then direct evaluation will be much faster than compiling (via compile) and evaluating a standard Julia function.

Compare with functor.


Parse a wiring diagram from a Julia program.

For the most part, this is standard Julia code but a few liberties are taken with the syntax. Products are represented as tuples. So if x and y are variables of type $X$ and $Y$, then (x,y) has type $X ⊗ Y$. Also, both () and nothing are interpreted as the monoidal unit $I$.

Unlike standard Julia, the function call expressions f(x,y) and f((x,y)) are equivalent. Consequently, given morphisms $f: W → X ⊗ Y$ and $g: X ⊗ Y → Z$, the code

x, y = f(w)

is equivalent to g(f(w)). In standard Julia, at most one of these calls to g would be valid, unless g had multiple signatures.

The diagonals (copying and deleting) of a cartesian category are implicit in the Julia syntax: copying is variable reuse and deleting is variable non-use. For the codiagonals (merging and creating), a special syntax is provided, reinterpreting Julia's vector literals. The merging of x1 and x2 is represented by the vector [x1,x2] and creation by the empty vector []. For example, f([x1,x2]) translates to compose(mmerge(X),f).

This macro is a wrapper around parse_wiring_diagram.


Construct an undirected wiring diagram using relation notation.

Unlike the @program macro for directed wiring diagrams, this macro departs significantly from the usual semantics of the Julia programming language. Function calls with $n$ arguments are now interpreted as assertions that an $n$-ary relation holds at a particular point. For example, the composite of binary relations $R ⊆ X × Y$ and $S ⊆ Y × Z$ can be represented as an undirected wiring diagram by the macro call

@relation (x,z) where (x::X, y::Y, z::Z) begin

In general, the context in the where clause defines the set of junctions in the diagram and variable sharing defines the wiring of ports to junctions. If the where clause is omitted, the set of junctions is inferred from the variables used in the macro call.

The ports and junctions of the diagram may be typed or untyped, and the ports may be named or unnamed. Thus, four possible types of undirected wiring diagrams may be returned, with the type determined by the form of relation header:

  1. Untyped, unnamed: @relation (x,z) where (x,y,z) ...
  2. Typed, unnamed: @relation (x,z) where (x::X, y::Y, z::Z) ...
  3. Untyped, named: @relation (out1=x, out2=z) where (x,y,z) ...
  4. Typed, named: @relation (out=1, out2=z) where (x::X, y::Y, z::Z) ...

All four types of diagram are subtypes of RelationDiagram.


DSLs for defining categories, diagrams, and related structures.

Here "diagram" means diagram in the standard category-theoretic sense, not string diagram or wiring diagram. DSLs for constructing wiring diagrams are provided by other submodules.


Present a diagram in a given category.

Recall that a diagram in a category $C$ is a functor $F: J → C$ from a small category $J$ into $C$. Given the category $C$, this macro presents a diagram in $C$, i.e., constructs a finitely presented indexing category $J$ together with a functor $F: J → C$. This method of simultaneous definition is often more convenient than defining $J$ and $F$ separately, as could be accomplished by calling @fincat and then @finfunctor.

As an example, the limit of the following diagram consists of the paths of length two in a graph:

@diagram SchGraph begin
  (e₁, e₂)::E
  (t: e₁ → v)::tgt
  (s: e₂ → v)::src

Morphisms in the indexing category can be left unnamed, which is convenient for defining free diagrams (see also @free_diagram). For example, the following diagram is isomorphic to the previous one:

@diagram SchGraph begin
  (e₁, e₂)::E
  (e₁ → v)::tgt
  (e₂ → v)::src

Of course, unnamed morphisms cannot be referenced by name within the @diagram call or in other settings, which can sometimes be problematic.


Present a category by generators and relations.

The result is a finitely presented category (FinCat) represented by a graph, possibly with path equations. For example, the simplex category truncated to one dimension is:

@fincat begin
  V, E
  (δ₀, δ₁): V → E
  σ₀: E → V

  σ₀ ∘ δ₀ == id(V)
  σ₀ ∘ δ₁ == id(V)

The objects and morphisms must be uniquely named.


Define a functor between two finitely presented categories.

Such a functor is defined by sending the object and morphism generators of the domain category to generic object and morphism expressions in the codomain category. For example, the following functor embeds the schema for graphs into the schema for circular port graphs by ignoring the ports:

@finfunctor SchGraph SchCPortGraph begin
  V => Box
  E => Wire
  src => src ⨟ box
  tgt => tgt ⨟ box

Present a free diagram in a given category.

Recall that a free diagram in a category $C$ is a functor $F: J → C$ where $J$ is a free category on a graph, here assumed finite. This macro is functionally a special case of @diagram but changes the interpretation of equality expressions. Rather than interpreting them as equations between morphisms in $J$, equality expresions can be used to introduce anonymous morphisms in a "pointful" style. For example, the limit of the following diagram consists of the paths of length two in a graph:

@free_diagram SchGraph begin
  (e₁, e₂)::E
  tgt(e₁) == v
  src(e₂) == v

Anonymous objects can also be introduced. For example, the previous diagram is isomorphic to this one:

@free_diagram SchGraph begin
  (e₁, e₂)::E
  tgt(e₁) == src(e₂)

Some care must exercised when defining morphisms between diagrams with anonymous objects, since they cannot be referred to by name.


Construct a graph in a simple, declarative style.

The syntax is reminiscent of Graphviz. Each line a declares a vertex or set of vertices, or an edge. For example, the following defines a directed triangle:

@graph begin
  v0, v1, v2
  fst: v0 → v1
  snd: v1 → v2
  comp: v0 → v2

Vertices in the graph must be uniquely named, whereas edges names are optional.


Contravariantly migrate data from one acset to another.

This macro is shorthand for defining a data migration using the @migration macro and then calling the migrate function. If the migration will be used multiple times, it is more efficient to perform these steps separately, reusing the functor defined by @migration.

For more about the syntax and supported features, see @migration.


Define a contravariant data migration.

This macro provides a DSL to specify a contravariant data migration from $C$-sets to $D$-sets for given schemas $C$ and $D$. A data migration is defined by a functor from $D$ to a category of queries on $C$. Thus, every object of $D$ is assigned a query on $C$ and every morphism of $D$ is assigned a morphism of queries, in a compatible way. Example usages are in the unit tests. What follows is a technical reference.

Several categories of queries are supported by this macro:

  1. Trivial queries, specified by a single object of $C$. In this case, the macro simply defines a functor $D → C$ and is equivalent to @finfunctor or @diagram.
  2. Conjunctive queries, specified by a diagram in $C$ and evaluated as a finite limit.
  3. Gluing queries, specified by a diagram in $C$ and evaluated as a finite colimit. An important special case is linear queries, evaluated as a finite coproduct.
  4. Gluc queries (gluings of conjunctive queries), specified by a diagram of diagrams in $C$ and evaluated as a colimit of limits. An important special case is duc queries (disjoint unions of conjunctive queries), evaluated as a coproduct of limits.

The query category of the data migration is not specified explicitly but is inferred from the queries used in the macro call. Implicit conversion is performed: trivial queries can be coerced to conjunctive queries or gluing queries, and conjunctive queries and gluing queries can both be coerced to gluc queries. Due to the implicit conversion, the resulting functor out of $D$ has a single query type and thus a well-defined codomain.

Syntax for the right-hand sides of object assignments is:

  • a symbol, giving object of $C$ (query type: trivial)
  • @product ... (query type: conjunctive)
  • @unit (alias: @terminal, query type: conjunctive)
  • @join ... (alias: @limit, query type: conjunctive)
  • @cases ... (alias: @coproduct, query type: gluing)
  • @empty (alias: @initial, query type: gluing)
  • @glue ... (alias: @colimit, query type: gluing)

Thes query types supported by this macro generalize the kind of queries familiar from relational databases. Less familiar is the concept of a morphism between queries, derived from the concept of a morphism between diagrams in a category. A query morphism is given by a functor between the diagrams' indexing categories together with a natural transformation filling a triangle of the appropriate shape. From a practical standpoint, the most important thing to remember is that a morphism between conjunctive queries is contravariant with respect to the diagram shapes, whereas a morphism between gluing queries is covariant. TODO: Reference for more on this.