Seven Sketches Through Rust
The problem this chapter solves is:
Applied category theory can feel too large to connect to code. This chapter turns the seven major themes of Seven Sketches in Compositionality into small Rust blocks with tests.
The previous chapter introduced reusable structures such as functors, natural transformations, monoids, and local derivative rules. This chapter keeps the same reading style but changes scale. Instead of following the tiny ML pipeline, it takes seven broader ideas and asks how each one can be made concrete enough to inspect in Rust.
This chapter does not reproduce the paper. It gives executable handles for the ideas.
The repeated pattern is:
mathematical structure
-> Rust type
-> constructor or method
-> law check
The Rust lesson is:
newtypes + private fields + validation + explicit composition
The category-theory lesson is:
objects + relationships + composition + laws
Reader orientation: Treat each sketch as a small modeling exercise. You are not expected to know the full mathematical theory before reading the Rust. Start from the type, then read the constructor, then read the law check.
Chapter Outcomes
By the end of this chapter, you should be able to:
- name the one law, relation, or boundary each Rust sketch preserves,
- explain which part of the source text each sketch deliberately does not implement,
- transfer one sketch to a small software or ML design problem without overclaiming the mathematics.
What You Already Know
If you have modeled business rules, resource limits, database relationships, or state machines in Rust, you already know the software version of this chapter. The new idea is that applied category theory gives names to those compositional structures and asks which laws make them trustworthy.
Source Snapshots
The main module:
Source snapshot: src/sketches.rs
//! Rust models for the seven applied-category-theory sketches.
//!
//! This module is a study companion for *Seven Sketches in Compositionality*.
//! It does not try to encode all of category theory. Instead, each section
//! gives one small Rust model for the main structure of a sketch: orders,
//! resources, databases, co-design, signal flow, circuits, and logic of
//! behavior.
use crate::error::{CtError, CtResult};
/// A finite order used for the first sketch: observations can be refined into
/// features, scores, and decisions.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum InformationLevel {
Observation,
Feature,
Score,
Decision,
}
impl InformationLevel {
/// Returns true when information at this level can flow into `target`.
pub fn can_flow_to(self, target: Self) -> bool {
self <= target
}
/// Least upper bound in this small total order.
pub fn join(self, other: Self) -> Self {
self.max(other)
}
}
const INFORMATION_LEVELS: [InformationLevel; 4] = [
InformationLevel::Observation,
InformationLevel::Feature,
InformationLevel::Score,
InformationLevel::Decision,
];
/// Checks reflexivity and transitivity for the finite information order.
pub fn information_order_obeys_preorder_laws() -> bool {
for level in INFORMATION_LEVELS {
if !level.can_flow_to(level) {
return false;
}
}
for first in INFORMATION_LEVELS {
for second in INFORMATION_LEVELS {
for third in INFORMATION_LEVELS {
let premise = first.can_flow_to(second) && second.can_flow_to(third);
if premise && !first.can_flow_to(third) {
return false;
}
}
}
}
true
}
/// Number of concrete features in a tiny model.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct FeatureCount(usize);
impl FeatureCount {
pub fn new(value: usize) -> CtResult<Self> {
if value == 0 {
return Err(CtError::EmptyInput("feature count"));
}
Ok(Self(value))
}
pub fn value(&self) -> usize {
self.0
}
}
/// Number of abstract layers used to summarize feature capacity.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct LayerBudget(usize);
impl LayerBudget {
pub fn new(value: usize) -> CtResult<Self> {
if value == 0 {
return Err(CtError::EmptyInput("layer budget"));
}
Ok(Self(value))
}
pub fn value(&self) -> usize {
self.0
}
}
const FEATURES_PER_LAYER: usize = 4;
/// Abstracts concrete features to the minimum layer budget that can hold them.
pub fn abstract_to_layer_budget(features: FeatureCount) -> CtResult<LayerBudget> {
LayerBudget::new(features.value().div_ceil(FEATURES_PER_LAYER))
}
/// Concretizes an abstract layer budget back to feature capacity.
pub fn concretize_layer_budget(layers: LayerBudget) -> FeatureCount {
FeatureCount(layers.value() * FEATURES_PER_LAYER)
}
/// Checks the Galois-connection law for the feature/layer abstraction.
pub fn feature_layer_galois_law_holds(
features: FeatureCount,
layers: LayerBudget,
) -> CtResult<bool> {
let abstracted_fits = abstract_to_layer_budget(features)?.value() <= layers.value();
let concrete_fits = features.value() <= concretize_layer_budget(layers).value();
Ok(abstracted_fits == concrete_fits)
}
/// A non-negative amount in a resource bundle.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct ResourceAmount(usize);
impl ResourceAmount {
pub fn new(value: usize) -> Self {
Self(value)
}
pub fn value(&self) -> usize {
self.0
}
}
/// Compute and memory resources composed by component-wise addition.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResourceBundle {
compute: ResourceAmount,
memory: ResourceAmount,
}
impl ResourceBundle {
pub fn new(compute: ResourceAmount, memory: ResourceAmount) -> Self {
Self { compute, memory }
}
pub fn compute(&self) -> ResourceAmount {
self.compute
}
pub fn memory(&self) -> ResourceAmount {
self.memory
}
/// Monoidal product for independent resources.
pub fn tensor(&self, other: &Self) -> Self {
Self {
compute: ResourceAmount::new(self.compute.value() + other.compute.value()),
memory: ResourceAmount::new(self.memory.value() + other.memory.value()),
}
}
/// Resource preorder: supply can cover demand when every component is large
/// enough.
pub fn can_supply(&self, demand: &Self) -> bool {
self.compute >= demand.compute && self.memory >= demand.memory
}
}
/// Demonstrates monotonicity of the monoidal resource operation.
pub fn resource_tensor_is_monotone() -> bool {
let small_supply = ResourceBundle::new(ResourceAmount::new(2), ResourceAmount::new(8));
let large_supply = ResourceBundle::new(ResourceAmount::new(4), ResourceAmount::new(16));
let fixed = ResourceBundle::new(ResourceAmount::new(1), ResourceAmount::new(2));
large_supply.can_supply(&small_supply)
&& large_supply
.tensor(&fixed)
.can_supply(&small_supply.tensor(&fixed))
}
/// Identifier for a department row.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DepartmentId(usize);
impl DepartmentId {
pub fn new(value: usize) -> Self {
Self(value)
}
pub fn value(&self) -> usize {
self.0
}
}
/// Identifier for an employee row.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EmployeeId(usize);
impl EmployeeId {
pub fn new(value: usize) -> Self {
Self(value)
}
pub fn value(&self) -> usize {
self.0
}
}
/// A row in the employee table. The department field is the schema arrow
/// `Employee -> Department`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EmployeeRecord {
id: EmployeeId,
department: DepartmentId,
}
impl EmployeeRecord {
pub fn new(id: EmployeeId, department: DepartmentId) -> Self {
Self { id, department }
}
pub fn id(&self) -> EmployeeId {
self.id
}
pub fn department(&self) -> DepartmentId {
self.department
}
}
/// A tiny database instance: schema objects become sets of ids, and schema
/// arrows become functions between those sets.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompanyInstance {
departments: Vec<DepartmentId>,
employees: Vec<EmployeeRecord>,
}
impl CompanyInstance {
pub fn new(
departments: impl IntoIterator<Item = DepartmentId>,
employees: impl IntoIterator<Item = EmployeeRecord>,
) -> CtResult<Self> {
let departments = departments.into_iter().collect::<Vec<_>>();
let employees = employees.into_iter().collect::<Vec<_>>();
for employee in &employees {
if !departments.contains(&employee.department()) {
return Err(CtError::ShapeMismatch {
op: "database instance",
expected: String::from("employee departments exist in Department"),
got: format!("missing department {}", employee.department().value()),
});
}
}
Ok(Self {
departments,
employees,
})
}
pub fn departments(&self) -> &[DepartmentId] {
&self.departments
}
pub fn employees(&self) -> &[EmployeeRecord] {
&self.employees
}
pub fn department_of(&self, employee_id: EmployeeId) -> Option<DepartmentId> {
self.employees
.iter()
.find(|employee| employee.id() == employee_id)
.map(EmployeeRecord::department)
}
pub fn foreign_keys_resolve(&self) -> bool {
self.employees
.iter()
.all(|employee| self.departments.contains(&employee.department()))
}
}
/// Requests per second needed by a design.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Throughput(usize);
impl Throughput {
pub fn new(value: usize) -> CtResult<Self> {
if value == 0 {
return Err(CtError::EmptyInput("throughput"));
}
Ok(Self(value))
}
pub fn value(&self) -> usize {
self.0
}
}
/// Millisecond latency boundary.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct LatencyMs(usize);
impl LatencyMs {
pub fn new(value: usize) -> CtResult<Self> {
if value == 0 {
return Err(CtError::EmptyInput("latency"));
}
Ok(Self(value))
}
pub fn value(&self) -> usize {
self.0
}
}
/// Functional need in a co-design problem.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DesignRequirement {
minimum_throughput: Throughput,
maximum_latency: LatencyMs,
}
impl DesignRequirement {
pub fn new(minimum_throughput: Throughput, maximum_latency: LatencyMs) -> Self {
Self {
minimum_throughput,
maximum_latency,
}
}
}
/// Implementation offered by a candidate component.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ImplementationOffer {
throughput: Throughput,
latency: LatencyMs,
}
impl ImplementationOffer {
pub fn new(throughput: Throughput, latency: LatencyMs) -> Self {
Self {
throughput,
latency,
}
}
}
/// A Bool-valued feasibility relation between requirements and offers.
#[derive(Debug, Clone, Copy)]
pub struct FeasibilityRelation;
impl FeasibilityRelation {
pub fn relates(requirement: DesignRequirement, offer: ImplementationOffer) -> bool {
offer.throughput >= requirement.minimum_throughput
&& offer.latency <= requirement.maximum_latency
}
}
/// A scalar coefficient in a signal-flow matrix.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SignalCoefficient(i32);
impl SignalCoefficient {
pub fn new(value: i32) -> Self {
Self(value)
}
pub fn zero() -> Self {
Self(0)
}
pub fn value(&self) -> i32 {
self.0
}
fn add(self, other: Self) -> Self {
Self(self.value() + other.value())
}
fn multiply(self, other: Self) -> Self {
Self(self.value() * other.value())
}
}
/// Number of rows in a signal-flow matrix.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MatrixRows(usize);
impl MatrixRows {
pub fn new(value: usize) -> CtResult<Self> {
if value == 0 {
return Err(CtError::EmptyInput("matrix rows"));
}
Ok(Self(value))
}
pub fn value(&self) -> usize {
self.0
}
}
/// Number of columns in a signal-flow matrix.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MatrixCols(usize);
impl MatrixCols {
pub fn new(value: usize) -> CtResult<Self> {
if value == 0 {
return Err(CtError::EmptyInput("matrix columns"));
}
Ok(Self(value))
}
pub fn value(&self) -> usize {
self.0
}
}
/// Matrix semantics for a signal-flow graph.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SignalMatrix {
rows: MatrixRows,
cols: MatrixCols,
coefficients: Vec<Vec<SignalCoefficient>>,
}
impl SignalMatrix {
pub fn new(
rows: MatrixRows,
cols: MatrixCols,
coefficients: Vec<Vec<SignalCoefficient>>,
) -> CtResult<Self> {
if coefficients.len() != rows.value() {
return Err(CtError::ShapeMismatch {
op: "signal matrix",
expected: format!("{} rows", rows.value()),
got: format!("{} rows", coefficients.len()),
});
}
for row in &coefficients {
if row.len() != cols.value() {
return Err(CtError::ShapeMismatch {
op: "signal matrix",
expected: format!("{} columns", cols.value()),
got: format!("{} columns", row.len()),
});
}
}
Ok(Self {
rows,
cols,
coefficients,
})
}
pub fn rows(&self) -> MatrixRows {
self.rows
}
pub fn cols(&self) -> MatrixCols {
self.cols
}
pub fn coefficients(&self) -> &[Vec<SignalCoefficient>] {
&self.coefficients
}
/// Matrix composition. If `previous` is `A -> B` and `self` is `B -> C`,
/// the result is `A -> C`.
pub fn compose_after(&self, previous: &Self) -> CtResult<Self> {
if previous.rows.value() != self.cols.value() {
return Err(CtError::ShapeMismatch {
op: "signal matrix composition",
expected: format!("middle dimension {}", self.cols.value()),
got: format!("middle dimension {}", previous.rows.value()),
});
}
let mut coefficients =
vec![vec![SignalCoefficient::zero(); previous.cols.value()]; self.rows.value()];
for (output_row, output_coefficients) in coefficients.iter_mut().enumerate() {
for (input_col, coefficient) in output_coefficients.iter_mut().enumerate() {
let mut total = SignalCoefficient::zero();
for middle in 0..self.cols.value() {
total = total.add(
self.coefficients[output_row][middle]
.multiply(previous.coefficients[middle][input_col]),
);
}
*coefficient = total;
}
}
Self::new(self.rows, previous.cols, coefficients)
}
}
/// A named boundary port in an open circuit.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PortName(&'static str);
impl PortName {
pub fn new(value: &'static str) -> CtResult<Self> {
if value.trim().is_empty() {
return Err(CtError::EmptyInput("port name"));
}
Ok(Self(value))
}
pub fn as_str(&self) -> &'static str {
self.0
}
}
/// Positive resistance value.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResistanceOhms(usize);
impl ResistanceOhms {
pub fn new(value: usize) -> CtResult<Self> {
if value == 0 {
return Err(CtError::InvalidQuantity {
kind: "resistance",
value: value as i64,
});
}
Ok(Self(value))
}
pub fn value(&self) -> usize {
self.0
}
}
/// A simple resistor component between two ports.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CircuitComponent {
from: PortName,
to: PortName,
resistance: ResistanceOhms,
}
impl CircuitComponent {
pub fn resistor(from: PortName, to: PortName, resistance: ResistanceOhms) -> Self {
Self {
from,
to,
resistance,
}
}
pub fn from(&self) -> PortName {
self.from
}
pub fn to(&self) -> PortName {
self.to
}
pub fn resistance(&self) -> ResistanceOhms {
self.resistance
}
}
/// An open circuit with input ports, output ports, and internal components.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OpenCircuit {
inputs: Vec<PortName>,
outputs: Vec<PortName>,
components: Vec<CircuitComponent>,
}
impl OpenCircuit {
pub fn new(
inputs: impl IntoIterator<Item = PortName>,
outputs: impl IntoIterator<Item = PortName>,
components: impl IntoIterator<Item = CircuitComponent>,
) -> CtResult<Self> {
let inputs = inputs.into_iter().collect::<Vec<_>>();
let outputs = outputs.into_iter().collect::<Vec<_>>();
let components = components.into_iter().collect::<Vec<_>>();
if inputs.is_empty() {
return Err(CtError::EmptyInput("circuit inputs"));
}
if outputs.is_empty() {
return Err(CtError::EmptyInput("circuit outputs"));
}
Ok(Self {
inputs,
outputs,
components,
})
}
pub fn input_count(&self) -> usize {
self.inputs.len()
}
pub fn output_count(&self) -> usize {
self.outputs.len()
}
pub fn component_count(&self) -> usize {
self.components.len()
}
/// Serial composition wires this circuit's outputs into the next circuit's
/// inputs.
pub fn then(&self, next: &Self) -> CtResult<Self> {
if self.output_count() != next.input_count() {
return Err(CtError::ShapeMismatch {
op: "open circuit serial composition",
expected: format!("{} next inputs", self.output_count()),
got: format!("{} next inputs", next.input_count()),
});
}
let mut components = self.components.clone();
components.extend_from_slice(&next.components);
Self::new(self.inputs.clone(), next.outputs.clone(), components)
}
/// Parallel composition keeps the two open interfaces side by side.
pub fn parallel(&self, other: &Self) -> CtResult<Self> {
let mut inputs = self.inputs.clone();
inputs.extend_from_slice(&other.inputs);
let mut outputs = self.outputs.clone();
outputs.extend_from_slice(&other.outputs);
let mut components = self.components.clone();
components.extend_from_slice(&other.components);
Self::new(inputs, outputs, components)
}
}
/// Truth values used for behavior classification.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TruthValue {
False,
True,
}
impl TruthValue {
pub fn and(self, other: Self) -> Self {
match (self, other) {
(TruthValue::True, TruthValue::True) => TruthValue::True,
_ => TruthValue::False,
}
}
pub fn implies(self, other: Self) -> Self {
match (self, other) {
(TruthValue::True, TruthValue::False) => TruthValue::False,
_ => TruthValue::True,
}
}
}
/// Discrete time point in a behavior trace.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct TimeTick(usize);
impl TimeTick {
pub fn new(value: usize) -> Self {
Self(value)
}
pub fn value(&self) -> usize {
self.0
}
}
/// Closed interval of time ticks.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TimeInterval {
start: TimeTick,
end: TimeTick,
}
impl TimeInterval {
pub fn new(start: TimeTick, end: TimeTick) -> CtResult<Self> {
if start > end {
return Err(CtError::InvalidInterval {
start: start.value(),
end: end.value(),
});
}
Ok(Self { start, end })
}
pub fn start(&self) -> TimeTick {
self.start
}
pub fn end(&self) -> TimeTick {
self.end
}
}
/// Local safety result on one interval.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LocalSafetyCheck {
interval: TimeInterval,
truth: TruthValue,
}
impl LocalSafetyCheck {
pub fn new(interval: TimeInterval, truth: TruthValue) -> Self {
Self { interval, truth }
}
pub fn interval(&self) -> TimeInterval {
self.interval
}
pub fn truth(&self) -> TruthValue {
self.truth
}
}
/// A cover of local behavior checks. The global result is true only when all
/// local checks are true.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SafetyCover(Vec<LocalSafetyCheck>);
impl SafetyCover {
pub fn new(checks: impl IntoIterator<Item = LocalSafetyCheck>) -> CtResult<Self> {
let checks = checks.into_iter().collect::<Vec<_>>();
if checks.is_empty() {
return Err(CtError::EmptyInput("safety cover"));
}
Ok(Self(checks))
}
pub fn global_truth(&self) -> TruthValue {
self.0
.iter()
.fold(TruthValue::True, |truth, check| truth.and(check.truth()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn information_order_has_preorder_laws() {
assert!(information_order_obeys_preorder_laws());
assert_eq!(
InformationLevel::Feature.join(InformationLevel::Decision),
InformationLevel::Decision
);
}
#[test]
fn feature_layer_abstraction_obeys_galois_law() -> CtResult<()> {
let features = FeatureCount::new(9)?;
let layers = LayerBudget::new(3)?;
assert!(feature_layer_galois_law_holds(features, layers)?);
assert_eq!(abstract_to_layer_budget(features)?.value(), 3);
assert_eq!(concretize_layer_budget(layers).value(), 12);
Ok(())
}
#[test]
fn resource_tensor_is_order_preserving() {
assert!(resource_tensor_is_monotone());
}
#[test]
fn database_instance_resolves_schema_arrow() -> CtResult<()> {
let research = DepartmentId::new(1);
let platform = DepartmentId::new(2);
let ada = EmployeeId::new(7);
let instance =
CompanyInstance::new([research, platform], [EmployeeRecord::new(ada, research)])?;
assert!(instance.foreign_keys_resolve());
assert_eq!(instance.department_of(ada), Some(research));
Ok(())
}
#[test]
fn database_instance_rejects_missing_department_reference() {
let research = DepartmentId::new(1);
let missing_department = DepartmentId::new(99);
let ada = EmployeeId::new(7);
assert!(matches!(
CompanyInstance::new([research], [EmployeeRecord::new(ada, missing_department)]),
Err(CtError::ShapeMismatch {
op: "database instance",
..
})
));
}
#[test]
fn feasibility_relation_matches_requirement_to_offer() -> CtResult<()> {
let requirement = DesignRequirement::new(Throughput::new(100)?, LatencyMs::new(80)?);
let offer = ImplementationOffer::new(Throughput::new(120)?, LatencyMs::new(50)?);
assert!(FeasibilityRelation::relates(requirement, offer));
Ok(())
}
#[test]
fn signal_matrices_compose_like_flow_graph_semantics() -> CtResult<()> {
let duplicate = SignalMatrix::new(
MatrixRows::new(2)?,
MatrixCols::new(1)?,
vec![
vec![SignalCoefficient::new(1)],
vec![SignalCoefficient::new(1)],
],
)?;
let add_weighted = SignalMatrix::new(
MatrixRows::new(1)?,
MatrixCols::new(2)?,
vec![vec![SignalCoefficient::new(2), SignalCoefficient::new(3)]],
)?;
let composed = add_weighted.compose_after(&duplicate)?;
assert_eq!(composed.coefficients(), &[vec![SignalCoefficient::new(5)]]);
Ok(())
}
#[test]
fn signal_matrix_composition_rejects_mismatched_middle_dimension() -> CtResult<()> {
let previous = SignalMatrix::new(
MatrixRows::new(2)?,
MatrixCols::new(1)?,
vec![
vec![SignalCoefficient::new(1)],
vec![SignalCoefficient::new(1)],
],
)?;
let next = SignalMatrix::new(
MatrixRows::new(1)?,
MatrixCols::new(3)?,
vec![vec![
SignalCoefficient::new(1),
SignalCoefficient::new(1),
SignalCoefficient::new(1),
]],
)?;
assert!(matches!(
next.compose_after(&previous),
Err(CtError::ShapeMismatch {
op: "signal matrix composition",
..
})
));
Ok(())
}
#[test]
fn open_circuits_compose_in_series_and_parallel() -> CtResult<()> {
let input = PortName::new("input")?;
let middle = PortName::new("middle")?;
let output = PortName::new("output")?;
let first = OpenCircuit::new(
[input],
[middle],
[CircuitComponent::resistor(
input,
middle,
ResistanceOhms::new(10)?,
)],
)?;
let second = OpenCircuit::new(
[middle],
[output],
[CircuitComponent::resistor(
middle,
output,
ResistanceOhms::new(20)?,
)],
)?;
let serial = first.then(&second)?;
let parallel = first.parallel(&second)?;
assert_eq!(serial.input_count(), 1);
assert_eq!(serial.output_count(), 1);
assert_eq!(serial.component_count(), 2);
assert_eq!(parallel.input_count(), 2);
assert_eq!(parallel.output_count(), 2);
Ok(())
}
#[test]
fn open_circuit_serial_composition_rejects_boundary_mismatch() -> CtResult<()> {
let input = PortName::new("input")?;
let output = PortName::new("output")?;
let left = OpenCircuit::new([input], [output], [])?;
let right = OpenCircuit::new([input, output], [output], [])?;
assert!(matches!(
left.then(&right),
Err(CtError::ShapeMismatch {
op: "open circuit serial composition",
..
})
));
Ok(())
}
#[test]
fn local_behavior_truth_glues_to_global_truth() -> CtResult<()> {
let first = LocalSafetyCheck::new(
TimeInterval::new(TimeTick::new(0), TimeTick::new(5))?,
TruthValue::True,
);
let second = LocalSafetyCheck::new(
TimeInterval::new(TimeTick::new(5), TimeTick::new(10))?,
TruthValue::True,
);
let cover = SafetyCover::new([first, second])?;
assert_eq!(cover.global_truth(), TruthValue::True);
assert_eq!(
TruthValue::True.implies(TruthValue::False),
TruthValue::False
);
Ok(())
}
}
The runnable companion:
Source snapshot: examples/05_seven_sketches.rs
use category_theory_transformer_rs::{
CircuitComponent, CompanyInstance, CtResult, DepartmentId, DesignRequirement, EmployeeId,
EmployeeRecord, FeasibilityRelation, FeatureCount, ImplementationOffer, InformationLevel,
LatencyMs, LayerBudget, LocalSafetyCheck, MatrixCols, MatrixRows, OpenCircuit, PortName,
ResistanceOhms, ResourceAmount, ResourceBundle, SafetyCover, SignalCoefficient, SignalMatrix,
Throughput, TimeInterval, TimeTick, TruthValue, abstract_to_layer_budget,
concretize_layer_budget, feature_layer_galois_law_holds, information_order_obeys_preorder_laws,
resource_tensor_is_monotone,
};
fn main() -> CtResult<()> {
println!(
"orders obey preorder laws: {}",
information_order_obeys_preorder_laws()
);
println!(
"join(feature, decision): {:?}",
InformationLevel::Feature.join(InformationLevel::Decision)
);
let features = FeatureCount::new(9)?;
let layers = LayerBudget::new(3)?;
println!(
"feature/layer Galois law: {}",
feature_layer_galois_law_holds(features, layers)?
);
println!(
"abstract 9 features to {} layers; concretize 3 layers to {} features",
abstract_to_layer_budget(features)?.value(),
concretize_layer_budget(layers).value()
);
let encoder = ResourceBundle::new(ResourceAmount::new(2), ResourceAmount::new(8));
let decoder = ResourceBundle::new(ResourceAmount::new(3), ResourceAmount::new(10));
println!("combined resource bundle: {:?}", encoder.tensor(&decoder));
println!(
"resource tensor monotone: {}",
resource_tensor_is_monotone()
);
let research = DepartmentId::new(1);
let platform = DepartmentId::new(2);
let ada = EmployeeId::new(7);
let instance =
CompanyInstance::new([research, platform], [EmployeeRecord::new(ada, research)])?;
println!(
"employee {:?} belongs to department {:?}",
ada,
instance.department_of(ada)
);
let requirement = DesignRequirement::new(Throughput::new(100)?, LatencyMs::new(80)?);
let offer = ImplementationOffer::new(Throughput::new(120)?, LatencyMs::new(50)?);
println!(
"co-design offer feasible: {}",
FeasibilityRelation::relates(requirement, offer)
);
let duplicate = SignalMatrix::new(
MatrixRows::new(2)?,
MatrixCols::new(1)?,
vec![
vec![SignalCoefficient::new(1)],
vec![SignalCoefficient::new(1)],
],
)?;
let add_weighted = SignalMatrix::new(
MatrixRows::new(1)?,
MatrixCols::new(2)?,
vec![vec![SignalCoefficient::new(2), SignalCoefficient::new(3)]],
)?;
let composed = add_weighted.compose_after(&duplicate)?;
println!(
"signal-flow matrix semantics: {:?}",
composed.coefficients()
);
let input = PortName::new("input")?;
let middle = PortName::new("middle")?;
let output = PortName::new("output")?;
let first_circuit = OpenCircuit::new(
[input],
[middle],
[CircuitComponent::resistor(
input,
middle,
ResistanceOhms::new(10)?,
)],
)?;
let second_circuit = OpenCircuit::new(
[middle],
[output],
[CircuitComponent::resistor(
middle,
output,
ResistanceOhms::new(20)?,
)],
)?;
println!(
"serial circuit component count: {}",
first_circuit.then(&second_circuit)?.component_count()
);
let safety = SafetyCover::new([
LocalSafetyCheck::new(
TimeInterval::new(TimeTick::new(0), TimeTick::new(5))?,
TruthValue::True,
),
LocalSafetyCheck::new(
TimeInterval::new(TimeTick::new(5), TimeTick::new(10))?,
TruthValue::True,
),
])?;
println!("global behavior truth: {:?}", safety.global_truth());
println!();
println!("Typed transformation:");
println!("InformationLevel <= InformationLevel checks preorder");
println!("FeatureCount <-> LayerBudget checks Galois law");
println!("ResourceBundle x ResourceBundle -> ResourceBundle");
println!("EmployeeRecord -> DepartmentId must resolve in CompanyInstance");
println!("DesignRequirement x ImplementationOffer -> bool");
println!("SignalMatrix x SignalMatrix -> SignalMatrix when dimensions match");
println!("OpenCircuit x OpenCircuit -> OpenCircuit when ports match");
println!("SafetyCover -> TruthValue");
Ok(())
}
Paper Map To Rust
Use this table as the navigation layer.
| Paper area | Main content | Rust companion |
|---|---|---|
| Generative effects | preorders, monotone maps, Galois connections | InformationLevel, FeatureCount, LayerBudget |
| Resources | monoidal preorders, resource composition, enrichment | ResourceBundle, ResourceAmount |
| Databases | schemas as categories, instances as functors | CompanyInstance, EmployeeRecord, DepartmentId |
| Co-design | feasibility relations and profunctor-like reasoning | DesignRequirement, ImplementationOffer, FeasibilityRelation |
| Signal flow | syntax, semantics, matrices, composition | SignalMatrix, SignalCoefficient |
| Circuits | open systems, ports, serial and parallel composition | OpenCircuit, CircuitComponent, PortName |
| Logic of behavior | truth values, intervals, local-to-global checks | TruthValue, TimeInterval, SafetyCover |
Each section below uses the same three-part lens:
Rust syntax
ML or software concept
Category theory concept
Source Scope Contract
This chapter is a study companion, not a replacement for the source text. Each Rust model preserves one inspectable idea and deliberately leaves the larger mathematical development outside the tiny example.
| Paper area | What the Rust sketch preserves | What it does not claim |
|---|---|---|
| Generative effects | order laws and one Galois-style capacity law | a complete treatment of generative effects |
| Resources | componentwise resource composition plus monotonicity | full enriched category theory |
| Databases | schema-like reference integrity through typed IDs | a general database semantics framework |
| Co-design | feasibility as a relation between requirements and offers | full profunctor theory |
| Signal flow | matrix composition and middle-dimension checks | a complete syntax-and-semantics account for signal-flow graphs |
| Circuits | open interfaces and serial boundary matching | a full circuit algebra |
| Logic of behavior | local interval truth combined into a global claim | sheaf theory or a full temporal logic |
Use the table as a precision guard. When the Rust code checks one law, say which law it checks. When the source paper develops a larger theory, do not pretend the small Rust model has implemented all of it.
PDF-To-Rust Reading Contract
The arXiv record describes Seven Sketches in Compositionality as a long invitation to applied category theory, built around concrete examples and seven major sketches. That matters for how to use this chapter. The goal is not to compress every page into a smaller page. The goal is to give every major sketch a Rust handle that a reader can run, inspect, and test.
Complete coverage in this companion chapter means:
every major sketch area has a Rust handle;
every Rust handle names one protected law, relation, or boundary;
every protected claim says what the source text still develops beyond the code;
every reader can run one command before arguing about the abstraction.
Use this ledger while reading the PDF beside the Rust:
| When the source text discusses | Ask in this chapter | Local evidence |
|---|---|---|
| an order, relation, or refinement | Which enum, newtype, or method names the ordered world? | InformationLevel::can_flow_to, FeatureCount, LayerBudget |
| a resource or compositional quantity | Which operation combines independent pieces? | ResourceBundle::tensor |
| a schema, instance, or reference | Which constructor rejects invalid references? | CompanyInstance::new |
| a feasibility relation | Which requirement-offer pair is accepted or rejected? | FeasibilityRelation::relates |
| a signal-flow or matrix composition | Which middle dimension must match? | SignalMatrix::compose_after |
| an open system or circuit interface | Which boundary ports must agree? | OpenCircuit::then |
| a local-to-global behavior claim | Which local checks combine into a global result? | SafetyCover::global_truth |
Page-To-Rust Decision Ladder
Use this ladder when you are reading the PDF page by page and do not yet know what to implement.
| Source paragraph shape | First Rust move | Evidence to seek | Safe non-claim |
|---|---|---|---|
| definition or named object | create a newtype, enum, or struct with private fields | constructor rejects an impossible value | this is one typed domain object, not the whole theory |
| relation, order, or feasibility statement | write a method that returns bool or CtResult<T> | one passing case and one rejected case | this is one relation, not a complete semantics |
| composition rule | write a method that consumes two typed inputs | mismatched middle object, dimension, or port returns Err(...) | this is one composition boundary, not a general algebra |
| theorem, law, or proof step | write the smallest law test | the test names the exact law being checked | one passing test is local evidence, not a proof of the source text |
| worked example or application story | build a tiny fixture and print one output line | cargo run --example 05_seven_sketches shows the protected boundary | the fixture is an analogy, not a production model |
| richer machinery beyond the local handle | write a larger-claim-not-implemented sentence | the non-claim names the missing theory explicitly | the chapter cites the source; it does not compress it |
The ladder keeps the reading order concrete:
source paragraph shape
-> first Rust move
-> local evidence
-> safe non-claim
Example decisions:
| If the source section is about… | Do not start with… | Start with… |
|---|---|---|
| schemas and instances | a trait called Category | CompanyInstance::new rejecting a dangling department |
| signal-flow semantics | a full graph language | SignalMatrix::compose_after checking the middle dimension |
| open-system composition | a general operad library | OpenCircuit::then checking output/input ports |
This is how the chapter can cover the whole source at the level promised by the book: not every theorem becomes a library feature, but every major reading shape has a disciplined path toward a Rust handle, a test, and a non-claim.
This is also the limit of the chapter. If a PDF section develops a richer construction than the Rust handle, record the richer construction as context, not as something the code has proved. The safe sentence is:
The source develops a larger theory here.
This Rust handle checks one executable boundary from that theory.
Source-Backed Precision Rules
This chapter uses external sources as scope guards. Each source supports a
limited teaching claim, and each claim is tied to one local Rust boundary or
test. The chapter does not claim that src/sketches.rs implements the full
source text, a general categorical semantics library, or a production ML
architecture theory.
The Bridge Back To Tiny ML section below is part of this source contract. Its check sentence is:
This sketch helps me reject this ML shortcut.
| Source | What the source supports | Local rule in this chapter | Rust evidence |
|---|---|---|---|
| Seven Sketches | Applied category theory can be introduced through concrete examples such as databases, circuits, dynamical systems, and other real-world structures. | Treat every sketch as one executable handle for one source idea, not as a replacement for the full mathematical development. | InformationLevel, ResourceBundle, CompanyInstance, OpenCircuit, SafetyCover |
| MIT Applied Category Theory OCW | The seven topic areas can be studied as a course sequence: orders, resources, databases, co-design, signal flow, circuits, and logic of behavior. | Keep the chapter order and Paper Map To Rust aligned with that applied-category sequence. | cargo run --example 05_seven_sketches |
| Category Theory for Programming | Category-theory vocabulary can be taught through programming-shaped structures. | Explain the programming boundary before naming the category-theory pattern. | information_order_obeys_preorder_laws, feature_layer_galois_law_holds, resource_tensor_is_monotone |
| Compositional Deep Learning | Categorical schemas, functorial structure, and composition invariants can appear in neural-network settings under stated assumptions. | Use the database and co-design sketches as ML transfer analogies only; do not claim the crate learns functors or implements the thesis. | CompanyInstance, FeasibilityRelation, database_instance_rejects_missing_department_reference |
| Categorical Deep Learning | Architecture discussions can separate constraints a model should satisfy from implementations that realize them. | Use co-design as a tiny Requirement x Offer -> Bool boundary, not as a theory of all neural architectures. | DesignRequirement, ImplementationOffer, FeasibilityRelation::relates |
| Rust Book: Enums | Enums encode values that must be one variant from a finite set. | Use enums for finite state-like domains before adding laws around them. | InformationLevel, TruthValue |
| Rust Book: Traits | Traits name shared behavior and make contracts explicit. | Treat laws and methods as contracts that tests must witness, not as prose-only claims. | SignalMatrix::compose_after, OpenCircuit::then, SafetyCover::global_truth |
The transfer pattern is:
source idea -> local Rust model -> law or boundary check
For this chapter, that means reading cargo run --example 05_seven_sketches and cargo test sketches::tests as evidence for the tiny
models above, not as evidence that the full seven-sketch source text,
categorical deep-learning literature, or every applied-category construction
has been implemented.
Choose A Sketch Without Losing The Tiny ML Thread
The source paper deliberately tours many application areas. This companion chapter keeps that breadth, but the book still has one main learning path: small typed systems that make ML structure inspectable.
Use this table to decide how deeply to read each sketch on a first pass.
| Sketch | Read deeply when you need | Tiny ML transfer | Safe first-pass treatment |
|---|---|---|---|
| Information order | staged representations or approval states | raw text, tokens, features, scores, and decisions form ordered levels of processed information | Core transfer |
| Feature/layer planning | two views of model capacity | feature counts and layer budgets are different descriptions of model size | Optional but useful |
| Resources | deployment or training constraints | compute and memory limits constrain model choices | Optional but practical |
| Database instance | structured training data | bad references in source data should fail before training | Core transfer |
| Co-design feasibility | requirements versus implementations | a model may satisfy some accuracy, latency, or memory requirements and fail others | Core transfer |
| Signal matrices | linear maps and shape compatibility | composed linear stages need matching middle dimensions | Core transfer |
| Open circuits | component interfaces | typed ML components compose only when output and input boundaries match | Core transfer |
| Logic of behavior | local checks and global claims | every batch or interval must satisfy the invariant before the global claim is trusted | Optional but useful |
On a first reading, focus on the rows marked core transfer. They connect most directly to the tiny ML pipeline. The optional rows are not less important; they are just farther from the first runnable model.
The chapter is successful if you can leave each sketch with one sentence:
This Rust model prevents this invalid composition.
Worked Example: Ordering Information Levels
The smallest first-principles version of this chapter is an ordered enum. Rust can derive an order for enum variants, and that gives the code a concrete way to ask whether one information level can safely flow into another:
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum Level {
Observation,
Feature,
Decision,
}
assert!(Level::Observation <= Level::Decision);
assert!(Level::Feature <= Level::Feature);
}
The real src/sketches.rs module uses the same idea with named domain types,
validation, and law checks.
Self-Check
Before reading the first sketch, explain why Observation <= Decision is a
modeling rule, not just a comparison between enum variants.
One Method Across Seven Sketches
Do not read the chapter as seven unrelated theory notes. Each sketch follows the same modeling method:
engineering problem
-> named Rust values
-> validated construction
-> composition operation or relation
-> law or boundary test
That method is the same one used in the tiny ML pipeline. The difference is the domain. Instead of tokens, logits, and parameters, this chapter uses resources, database rows, signal matrices, open circuits, and local behavior checks.
When a sketch feels abstract, ask one question:
What invalid composition should this model prevent?
The answer usually points to the real engineering value of the categorical shape.
The chapter’s applied models can be scanned by the structure they protect:
| Sketch | Rust handle | Valid structure | Rejected or checked boundary |
|---|---|---|---|
| Information order | InformationLevel | information flows upward through refinement levels | preorder laws check reflexivity and transitivity |
| Feature/layer planning | FeatureCount, LayerBudget | concrete and abstract capacity views agree | Galois law checks both directions of fit |
| Resources | ResourceBundle | compute and memory combine componentwise | monotonicity checks supply still respects demand |
| Databases | CompanyInstance | employee records refer to known departments | missing department references return Err(...) |
| Co-design | FeasibilityRelation | offers satisfy throughput and latency requirements | infeasible offers are not related |
| Signal flow | SignalMatrix | matrices compose when middle dimensions match | mismatched dimensions return Err(...) |
| Open circuits | OpenCircuit | serial composition connects matching port boundaries | boundary mismatch returns Err(...) |
| Behavior logic | SafetyCover | local interval checks combine into global truth | unknown local truth prevents a false global guarantee |
This table is the chapter’s law-and-boundary index. The point is not to memorize eight rows. The point is to see that each sketch earns its abstraction by protecting one concrete relationship.
Bridge Back To Tiny ML
The source text and the MIT course both use applied examples to make category theory portable across domains. This chapter uses the same move in a smaller way: each sketch should transfer back to one tiny ML design pressure.
Use the bridge below when the chapter starts to feel like a separate category theory tour.
| Sketch | Tiny ML pressure | Rust handle to inspect | Bad shortcut the sketch helps reject | Safe non-claim |
|---|---|---|---|---|
| Information order | raw observations, features, scores, and decisions should not be interchangeable | InformationLevel::can_flow_to | treating a score as if it were already a decision | the enum is a teaching model, not a calibrated decision theory |
| Feature/layer planning | concrete feature count and abstract model capacity are different views | FeatureCount, LayerBudget | treating abstraction and concretization as inverse functions | the Galois-style law is a tiny planning check, not a full architecture search method |
| Resources | training and inference choices depend on compute and memory together | ResourceBundle::tensor | collapsing all resource constraints into one raw number | the bundle is a two-resource sketch, not a deployment cost model |
| Database instance | training rows should not carry dangling references into feature extraction | CompanyInstance::new | letting malformed structured data reach the model | this validates one schema arrow, not a full data platform |
| Co-design feasibility | model choice is often a relation between requirements and candidate implementations | FeasibilityRelation::relates | forcing every design question into a single function | one feasible offer is not proof that every implementation satisfies the constraint |
| Signal matrices | linear stages compose only when dimensions line up | SignalMatrix::compose_after | multiplying stages before checking the middle dimension | this is matrix composition, not a full autodiff or neural-network framework |
| Open circuits | components need explicit input and output boundaries | OpenCircuit::then | wiring pieces by name while ignoring boundary shape | the circuit model is an interface analogy, not a full circuit algebra |
| Logic of behavior | local checks must support global claims | SafetyCover::global_truth | claiming global safety while one local interval failed | conjunction over intervals is a tiny behavior check, not full sheaf theory |
Read each row as a transfer sentence:
This sketch helps me reject this ML shortcut.
That sentence is the practical reason to keep the sketch in the book. The formal category-theory vocabulary matters only after the engineering shortcut is visible.
Transfer Triage Card
Use this card when a source idea feels too large to turn into code. The goal is not to shrink the source. The goal is to choose one local boundary that can be inspected.
| If the transfer feels like… | Do this first | Ready when you can write… |
|---|---|---|
| a broad theory claim | shrink to one law, relation, or boundary | Source claim -> local Rust handle |
| a vocabulary list | choose one constructor, method, example line, or test | Rust handle -> protected relationship |
| a passing example only | name the invalid case it rejects or the law it checks | protected relationship -> rejected shortcut |
| an ML analogy | name the ML object, constraint, or composition it maps to | tiny ML transfer -> safe non-claim |
| a research source | state what the source does not license this chapter to claim | non-claim -> evidence command |
The completed transfer card has seven fields:
source idea:
local Rust handle:
protected law, relation, or boundary:
invalid shortcut rejected:
tiny ML transfer:
larger claim not implemented:
local evidence command or test:
Example:
source idea: schemas and instances
local Rust handle: CompanyInstance::new
protected law, relation, or boundary: EmployeeRecord -> DepartmentId must resolve
invalid shortcut rejected: letting a missing department reach feature extraction
tiny ML transfer: validate structured training rows before training
larger claim not implemented: a general categorical database semantics
local evidence command or test: cargo test sketches::tests --lib
Second example:
source idea: open systems have boundaries
local Rust handle: OpenCircuit::then
protected law, relation, or boundary: previous outputs must match next inputs
invalid shortcut rejected: wiring components by label while ignoring shape
tiny ML transfer: Tokenizer -> Embedder is legal only when the output object
matches the next input object
larger claim not implemented: decorated cospans, hypergraph categories, and
operads for general circuit composition
local evidence command or test: cargo test sketches::tests --lib
This follows the source discipline used above. Seven Sketches gives a broad tour through concrete examples. MIT’s course frames category theory as a way to organize formal systems and transfer knowledge between them. Categorical deep-learning work distinguishes architecture constraints from implementations. The local job here is smaller: choose one implementable handle, name one protected relationship, and state the non-claim.
Sketch 1: Information Order
The problem this block solves is:
Some concepts are ordered by refinement. An observation can be refined into a feature, a feature into a score, and a score into a decision.
The block begins:
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum InformationLevel {
Observation,
Feature,
Score,
Decision,
}
Rust Syntax
This is an enum.
The variants are ordered because the enum derives:
PartialOrd, Ord
That means Rust can compare:
InformationLevel::Observation <= InformationLevel::Decision
The methods:
pub fn can_flow_to(self, target: Self) -> bool {
self <= target
}
pub fn join(self, other: Self) -> Self {
self.max(other)
}
reuse that ordering.
can_flow_to checks whether information can move upward.
join returns the more informative of two levels in this total order.
ML Or Software Concept
ML systems often move through levels of processed information:
raw observation
-> extracted feature
-> model score
-> final decision
The order prevents treating a low-level observation as if it were already a decision.
Category Theory Concept
This is a preorder-shaped example.
A preorder needs:
reflexivity: a <= a
transitivity: if a <= b and b <= c, then a <= c
The law check:
information_order_obeys_preorder_laws()
iterates over the finite set and verifies those rules.
Transfer Task: Ordered States
Model a workflow from your own codebase as an ordered enum, such as:
enum ReviewState {
Draft,
Reviewed,
Published,
}
Then write the Rust sentence you would want to be true:
Draft can flow to Published
Published cannot flow to Draft
state can always flow to itself
The transfer is complete when you can name the invalid flow your order prevents.
Sketch 1 Continued: Feature And Layer Galois Law
The problem this block solves is:
A concrete feature count and an abstract layer budget are different worlds, but they can be coordinated by a law.
The key types:
pub struct FeatureCount(usize);
pub struct LayerBudget(usize);
The conversion functions:
pub fn abstract_to_layer_budget(features: FeatureCount) -> CtResult<LayerBudget>
pub fn concretize_layer_budget(layers: LayerBudget) -> FeatureCount
Rust Syntax
Both FeatureCount and LayerBudget are newtypes around usize.
Their constructors reject zero because neither a zero feature count nor a zero layer budget is useful in this model.
abstract_to_layer_budget divides feature count by FEATURES_PER_LAYER and
rounds up:
features.value().div_ceil(FEATURES_PER_LAYER)
concretize_layer_budget multiplies layers by features per layer.
ML Or Software Concept
This models capacity planning.
Concrete features might be:
9 measured feature channels
The abstract layer budget might be:
3 layers
The law says the two planning views agree about what fits.
Category Theory Concept
The checked law is:
abstract(features) <= layers
if and only if
features <= concretize(layers)
That is the shape of a Galois connection.
The two directions are not inverses.
They are coordinated by an order law.
Transfer Task: Concrete And Abstract Capacity
Pick one concrete measure and one abstract budget from software work:
concrete: batch size, feature count, request rate
abstract: GPU count, layer budget, service tier
Write the two functions:
abstract(concrete) -> abstract budget
concretize(abstract budget) -> concrete capacity
The transfer is complete when you can state the law in both directions:
abstract(concrete) fits budget
if and only if
concrete fits concretize(budget)
Sketch 2: Resources
The problem this block solves is:
Independent resources need a way to combine, and resource supply must respect demand ordering.
The key block:
pub struct ResourceBundle {
compute: ResourceAmount,
memory: ResourceAmount,
}
Rust Syntax
ResourceAmount wraps a usize.
ResourceBundle stores two resource dimensions:
compute
memory
The monoidal operation is:
pub fn tensor(&self, other: &Self) -> Self
It adds compute to compute and memory to memory.
The preorder check is:
pub fn can_supply(&self, demand: &Self) -> bool
It returns true only when every resource component is large enough.
ML Or Software Concept
This is the shape of deployment capacity:
encoder resources + decoder resources = combined resources
A machine can satisfy a demand only when it has enough compute and memory.
Category Theory Concept
This is a monoidal preorder.
The preorder is can_supply.
The monoidal product is tensor.
The law check:
resource_tensor_is_monotone()
shows that adding the same fixed resource bundle to both sides preserves the order.
Transfer Task: Resource Bundle
Extend the idea to three resource dimensions:
compute
memory
disk
Name the constructor boundary and the law check:
ResourceBundle::new(compute, memory, disk)
resource_tensor_is_monotone
The transfer is complete when you can explain why componentwise addition should not turn an adequate supply into an inadequate supply.
Sketch 3: Database Instance
The problem this block solves is:
A database row with a foreign key should not point at a missing row.
The key types:
pub struct DepartmentId(usize);
pub struct EmployeeId(usize);
pub struct EmployeeRecord {
id: EmployeeId,
department: DepartmentId,
}
pub struct CompanyInstance {
departments: Vec<DepartmentId>,
employees: Vec<EmployeeRecord>,
}
Rust Syntax
DepartmentId and EmployeeId are distinct newtypes.
That prevents mixing department IDs and employee IDs.
EmployeeRecord contains:
employee id
department id
CompanyInstance::new collects departments and employees, then checks every
employee department exists:
if !departments.contains(&employee.department()) {
return Err(CtError::ShapeMismatch { ... });
}
ML Or Software Concept
Many ML systems depend on structured data.
If a row references missing data, downstream feature extraction or training can fail later in a confusing place.
This code rejects invalid relational structure at construction time.
Category Theory Concept
The schema can be read as:
Employee -> Department
An instance assigns sets of rows to schema objects.
The foreign key is a function from employees to departments.
CompanyInstance::new checks that the function is defined for every employee.
Transfer Task: Foreign Key Boundary
Translate the pattern to another schema arrow:
Task -> Project
Order -> Customer
Comment -> Post
Name two newtypes and one record:
ProjectId
TaskId
TaskRecord { id: TaskId, project: ProjectId }
The transfer is complete when you can write the constructor failure in plain English: “reject a task whose project id is missing from the project table.”
Sketch 4: Co-Design Feasibility
The problem this block solves is:
Some relationships are not functions. A requirement and an implementation offer are related only when constraints are satisfied.
The key types:
pub struct DesignRequirement {
minimum_throughput: Throughput,
maximum_latency: LatencyMs,
}
pub struct ImplementationOffer {
throughput: Throughput,
latency: LatencyMs,
}
pub struct FeasibilityRelation;
Rust Syntax
Throughput and LatencyMs are validated newtypes.
DesignRequirement stores the minimum acceptable throughput and maximum
acceptable latency.
ImplementationOffer stores what an implementation actually provides.
The relation is:
pub fn relates(requirement: DesignRequirement, offer: ImplementationOffer) -> bool {
offer.throughput >= requirement.minimum_throughput
&& offer.latency <= requirement.maximum_latency
}
ML Or Software Concept
This models feasibility:
Can this model/service/deployment satisfy this requirement?
For example:
required throughput: at least 100 requests/sec
required latency: at most 80 ms
offer: 120 requests/sec and 50 ms
The offer is feasible.
Category Theory Concept
This is relation-shaped rather than function-shaped.
It is the small Bool-valued version of profunctor-like reasoning:
Requirement x Offer -> Bool
Not every design problem should be forced into:
A -> B
Some should be modeled as constraints or relations.
This also gives a small handle for reading newer categorical deep-learning work. At architecture scale, a model can be discussed in terms of constraints it should satisfy and implementations that realize those constraints. This Rust sketch keeps only the first tiny version of that idea:
DesignRequirement x ImplementationOffer -> bool
The important distinction is:
| Question | Co-design sketch answer |
|---|---|
| What should be true? | throughput is high enough and latency is low enough |
| What implementation evidence do we have? | one ImplementationOffer value with checked throughput and latency |
| What does the relation decide? | whether that offer satisfies that requirement |
That is not a full theory of neural architectures. It is the learner-sized boundary that prevents a common overclaim: one implementation example is not the same thing as the whole constraint space.
Transfer Task: Feasibility Relation
Model a relation that is not a function:
Requirement x Offer -> Bool
For example, use:
minimum accuracy
maximum memory
maximum latency
Then write the architecture version:
ArchitectureConstraint x CandidateImplementation -> Bool
The transfer is complete when you can explain why many offers may satisfy one requirement, one offer may satisfy many requirements, and one passing offer is not proof that every future implementation satisfies the architecture constraint.
Sketch 5: Signal Matrices
The problem this block solves is:
Signal-flow diagrams need executable semantics. In this companion, matrices provide that meaning.
The key types:
pub struct SignalCoefficient(i32);
pub struct MatrixRows(usize);
pub struct MatrixCols(usize);
pub struct SignalMatrix {
rows: MatrixRows,
cols: MatrixCols,
coefficients: Vec<Vec<SignalCoefficient>>,
}
Rust Syntax
MatrixRows and MatrixCols reject zero.
SignalMatrix::new validates that the coefficient matrix has the promised
shape:
number of rows matches MatrixRows
number of columns in every row matches MatrixCols
The composition method:
pub fn compose_after(&self, previous: &Self) -> CtResult<Self>
requires compatible middle dimensions.
Then it performs matrix multiplication using:
add
multiply
sum over the middle dimension
ML Or Software Concept
This is the same shape as composing linear layers or signal-processing stages.
If one stage maps:
A -> B
and another maps:
B -> C
then the composite maps:
A -> C
The dimensions must line up.
Category Theory Concept
Signal-flow syntax gets matrix semantics.
The important principle is functorial semantics:
meaning(composed diagram)
=
composition of meanings
The code enforces the same middle-dimension law that ordinary morphism composition enforces.
Transfer Task: Shape-Safe Composition
Write the shape of two stages before writing any coefficients:
A -> B
B -> C
Then write one invalid pair:
A -> B
D -> C
The transfer is complete when you can name the exact middle dimension that must match for matrix composition to make sense.
Sketch 6: Open Circuits
The problem this block solves is:
A circuit is not only internal components. It also has a boundary where it can connect to other circuits.
The key types:
pub struct PortName(&'static str);
pub struct ResistanceOhms(usize);
pub struct CircuitComponent {
from: PortName,
to: PortName,
resistance: ResistanceOhms,
}
pub struct OpenCircuit {
inputs: Vec<PortName>,
outputs: Vec<PortName>,
components: Vec<CircuitComponent>,
}
Rust Syntax
PortName::new rejects empty names.
ResistanceOhms::new rejects zero resistance.
OpenCircuit::new rejects circuits with no inputs or no outputs.
Serial composition:
pub fn then(&self, next: &Self) -> CtResult<Self>
checks:
self output count == next input count
Parallel composition:
pub fn parallel(&self, other: &Self) -> CtResult<Self>
puts the two boundaries side by side.
ML Or Software Concept
This looks like component architecture:
input interface
internal implementation
output interface
Composition should fail when interfaces do not match.
That rule applies to services, data pipelines, neural layers, and circuit-like systems.
Category Theory Concept
This is the open-system idea.
The boundary is part of the object.
Composition is controlled by boundary compatibility.
The paper develops this with cospans, hypergraph categories, decorated cospans, and operads. The Rust code gives a small typed analogue.
Transfer Task: Interface Boundary
Model two components by boundary only:
Tokenizer: Text -> TokenSequence
Embedder: TokenSequence -> HiddenSequence
Then write one invalid serial composition:
Tokenizer: Text -> TokenSequence
Classifier: Logits -> Label
The transfer is complete when you can say which output boundary failed to match which input boundary.
Sketch 7: Logic Of Behavior
The problem this block solves is:
A system may be safe on local time intervals. The code needs a way to combine local safety checks into one global result.
The key types:
pub enum TruthValue {
False,
True,
}
pub struct TimeTick(usize);
pub struct TimeInterval {
start: TimeTick,
end: TimeTick,
}
pub struct LocalSafetyCheck {
interval: TimeInterval,
truth: TruthValue,
}
pub struct SafetyCover(Vec<LocalSafetyCheck>);
Rust Syntax
TruthValue implements Boolean-style operations:
and
implies
TimeInterval::new rejects intervals where start is after end.
SafetyCover::new rejects an empty list of checks.
global_truth folds all local truths with and:
self.0
.iter()
.fold(TruthValue::True, |truth, check| truth.and(check.truth()))
ML Or Software Concept
This models safety or behavior validation over time:
check interval 0..5
check interval 5..10
combine into global result
If every local check is true, global truth is true.
If any local check is false, global truth is false.
Category Theory Concept
This is a small analogue of local-to-global reasoning.
The sheaf-like idea is:
local facts can determine a global fact when they glue coherently
The code uses a simple conjunction model, not full sheaf theory.
The important lesson is that proof-like information becomes explicit data, not an informal comment.
Transfer Task: Local Checks To Global Claim
Pick one invariant over time:
loss is finite
latency stays below the budget
no batch has an empty token sequence
Split it into local intervals and assign a truth value to each interval.
The transfer is complete when you can explain why one false local check must make the global claim false.
Tests As Exercise Solutions
The problem this block solves is:
The laws should be runnable, not only described in prose.
The test module checks preorder laws, the feature/layer Galois law, resource tensor monotonicity, database foreign-key resolution, feasibility relation behavior, signal matrix composition, open circuit serial and parallel composition, and local-to-global truth.
It also includes negative boundary tests. A database instance with a missing department reference is rejected. Signal matrices with incompatible middle dimensions cannot compose. Open circuits with mismatched serial boundaries do not wire together. Those failures are part of the teaching point: the model is useful because it rejects incoherent structure near the boundary.
Rust Syntax
Every law is a normal Rust test marked with:
#[test]
Tests that may fail through constructors return:
CtResult<()>
so they can use ?.
ML Or Software Concept
The tests act as executable learning checks.
If a future change breaks a law, the project should fail quickly.
Category Theory Concept
The tests are small law checks.
They are not formal proofs, but they keep the implementation aligned with the claimed structure.
Run The Companion
Run:
cargo run --example 05_seven_sketches
The output gives one executable handle per sketch:
orders obey preorder laws: true
feature/layer Galois law: true
resource tensor monotone: true
employee EmployeeId(7) belongs to department Some(DepartmentId(1))
co-design offer feasible: true
signal-flow matrix semantics: [[SignalCoefficient(5)]]
serial circuit component count: 2
global behavior truth: True
Typed transformation:
InformationLevel <= InformationLevel checks preorder
FeatureCount <-> LayerBudget checks Galois law
ResourceBundle x ResourceBundle -> ResourceBundle
EmployeeRecord -> DepartmentId must resolve in CompanyInstance
DesignRequirement x ImplementationOffer -> bool
SignalMatrix x SignalMatrix -> SignalMatrix when dimensions match
OpenCircuit x OpenCircuit -> OpenCircuit when ports match
SafetyCover -> TruthValue
Example Output Transfer Checklist
Use the companion output as a boundary map. Each line should tell you which structure is being protected.
| Example output | Rust handle | Protected structure | Shortcut to reject |
|---|---|---|---|
orders obey preorder laws: true | InformationLevel | information can flow upward through an ordered refinement path | treating an enum order as arbitrary display order |
feature/layer Galois law: true | FeatureCount, LayerBudget | concrete and abstract capacity views agree by a two-way fit law | treating abstraction and concretization as inverse functions |
resource tensor monotone: true | ResourceBundle::tensor | adding resources componentwise preserves supply ordering | combining compute and memory as one raw number |
employee ... belongs to department ... | CompanyInstance::new | every employee department reference resolves | letting a missing foreign key reach later feature extraction |
co-design offer feasible: true | FeasibilityRelation::relates | requirements and offers form a relation, not a single function | forcing every design question into A -> B |
signal-flow matrix semantics: ... | SignalMatrix::compose_after | matrix meanings compose only when middle dimensions match | multiplying stages before checking shape |
serial circuit component count: 2 | OpenCircuit::then | serial composition requires matching boundaries | wiring components by name alone |
global behavior truth: True | SafetyCover::global_truth | local checks combine into a global claim | claiming global safety while one local interval is false |
The typed lines at the bottom of the output are not extra decoration. They name the form each sketch protects: order, Galois law, monoidal resource composition, database instance, feasibility relation, matrix composition, open system composition, and local-to-global truth.
For the full validation gate:
cargo test --all-targets --all-features
Core Mental Model
In Rust terms:
each sketch becomes concrete types, constructors, methods, and tests
In ML or software terms:
orders, resources, schemas, feasibility, signal flow, interfaces, and safety
are all engineering structures
In category-theory terms:
the useful part is compositionality: make structure visible, then make
composition obey laws
What To Remember
The seven sketches are not seven disconnected topics.
They repeat one engineering discipline:
name the objects
name the relationships
control construction
define composition
check the law
That is also the discipline used by the tiny ML pipeline in the rest of the course.
Where This Leaves Us
This chapter showed that the book’s main discipline is not limited to language modeling. Orders, resources, database instances, feasibility relations, signal matrices, open circuits, and local behavior checks can all be read in the same way: name the values, constrain construction, define composition, and test the law that makes the composition trustworthy.
The remaining practice material in Exercises asks you to use that reading method yourself. The exercises are not meant to test memorized definitions. They are meant to train the habit of translating one Rust block into its software role and its categorical shape.
Further Reading
Do not treat these sources as a separate theory shelf. Use them to improve one local Rust sentence at a time.
Start from this local evidence:
cargo run --example 05_seven_sketches
cargo test sketches::tests --lib
src/sketches.rs
examples/05_seven_sketches.rs
Then read the sources in this order:
| Source | What to transfer back into this chapter | Local evidence to inspect |
|---|---|---|
| Seven Sketches | Each sketch is one compositional structure: orders, resources, schemas, co-design, signal flow, open systems, or local-to-global behavior. | Paper Map To Rust, Source Scope Contract, Example Output Transfer Checklist |
| MIT Applied Category Theory OCW | The seven topic areas form a study sequence, not a bag of unrelated examples. | cargo run --example 05_seven_sketches output order |
| Category Theory for Programming | Programming examples should carry the vocabulary before the formal name is emphasized. | InformationLevel, ResourceBundle, SignalMatrix, OpenCircuit |
| Compositional Deep Learning | Categorical schemas and composition invariants can appear in neural-network settings under explicit assumptions. | CompanyInstance, FeasibilityRelation, database_instance_rejects_missing_department_reference |
| Categorical Deep Learning | Architecture-level claims should separate constraints from implementations that realize them. | DesignRequirement x ImplementationOffer -> bool |
| Rust Book: Enums | Finite domains are often clearer as enums than as unstructured numbers. | InformationLevel, TruthValue |
| Rust Book: Traits | Shared behavior should become an explicit contract before laws are tested around it. | SignalMatrix::compose_after, OpenCircuit::then, SafetyCover::global_truth |
After reading one source, answer four questions:
- Which Rust handle did it clarify?
- Which law, relation, or boundary did it support?
- Which larger claim did the source not license?
- Which command or test shows the local evidence?
For this chapter, the commands are:
cargo run --example 05_seven_sketches
cargo test sketches::tests --lib
For terminology recovery, use:
- References: paper links and supporting Rust/materials
- Glossary: terms used by the course
- Repository Source Snapshots: complete source files
If a source does not help you name a Rust handle, a law or boundary, a non-claim, and an evidence command, it has not transferred back into this chapter yet.
Practice After This Chapter
Use Exercise 10 to test one sketch law or inspect one negative test. The goal is to choose a structure, name the invalid state it rejects, and explain the result through Rust syntax, software or ML meaning, and category-theory shape.
Retrieval Practice
Recall
Recover the chapter map before choosing a transfer example.
Name the sketch that models ordered refinement from observation to decision.
Name the sketch that rejects an employee row whose department is missing.
Name the sketch that rejects matrix composition when the middle dimensions do not match.
Name the sketch that rejects serial component composition when output and input boundaries do not match.
Explain
Explain the protected boundary.
For InformationLevel, explain why Observation can flow to Decision but a
decision should not silently flow backward to an observation.
For CompanyInstance, explain why missing department references should be
rejected at construction time instead of during later feature extraction.
For SignalMatrix, explain why the middle dimension must match before matrix
composition can run.
For OpenCircuit, explain why a boundary mismatch is an interface error, not a
numeric error.
Apply
Use the companion output as evidence.
The example prints:
orders obey preorder laws: true
feature/layer Galois law: true
resource tensor monotone: true
signal-flow matrix semantics: [[SignalCoefficient(5)]]
serial circuit component count: 2
global behavior truth: True
Choose two printed lines. For each one, write:
Rust value or function:
software meaning:
category-theory shape:
invalid structure prevented or law checked:
Then pick one engineering concept from your own work, such as permissions, queues, schema references, resource budgets, or service interfaces. Describe the objects, relationships, and one law or boundary test you would want the code to check.
Debug
For each broken model, name the missing structure:
using raw usize for both EmployeeId and DepartmentId
letting a missing department reference enter the training data
composing signal matrices without checking the middle dimension
serially wiring components by name without checking boundary counts
claiming global safety when one local interval is false
A strong answer should use the chapter’s repeated method:
name the objects
name the relationships
control construction
define composition
check the law
The goal is not to memorize every sketch. The goal is to recognize what each model refuses to let pass as valid structure.