151 lines
5.0 KiB
Markdown
151 lines
5.0 KiB
Markdown
# Gategory theory as a graph
|
|
Some time ago I had a conversation with Gemini 2.5 pro, I believe, since they have backend access to youtube. The subject was regarding a specific series of videos that described category theory in basic terms that just about any dev familiar basic types and type theory could probably understand. I noticed that the relationships form a graph so after some back and forth about that I asked them to provide a sketch using graphology
|
|
|
|
```typescript
|
|
import { MultiGraph, type Attributes } from "npm:graphology"
|
|
|
|
// --- Setup and Types ---
|
|
|
|
interface ObjectAttributes extends Attributes {
|
|
name: string;
|
|
description?: string;
|
|
}
|
|
|
|
interface MorphismAttributes extends Attributes {
|
|
label: string;
|
|
implementation: (input: any) => any;
|
|
composedOf?: [string, string]; // Stores the history of the composition
|
|
}
|
|
|
|
const categoryGraph = new MultiGraph<ObjectAttributes, MorphismAttributes>();
|
|
|
|
// --- The Compose Function ---
|
|
|
|
/**
|
|
* Composes two morphisms (g after f) and adds the resulting morphism to the graph.
|
|
* @param fKey The key of the first morphism (A -> B).
|
|
* @param gKey The key of the second morphism (B -> C).
|
|
* @returns The key of the new composite morphism (A -> C).
|
|
*/
|
|
function compose(fKey: string, gKey: string): string {
|
|
// 1. & 2. Find edges and their attributes
|
|
if (!categoryGraph.hasEdge(fKey) || !categoryGraph.hasEdge(gKey)) {
|
|
throw new Error('One or both morphism keys do not exist in the graph.');
|
|
}
|
|
const f_attributes = categoryGraph.getEdgeAttributes(fKey);
|
|
const g_attributes = categoryGraph.getEdgeAttributes(gKey);
|
|
|
|
const f_source = categoryGraph.source(fKey);
|
|
const f_target = categoryGraph.target(fKey);
|
|
const g_source = categoryGraph.source(gKey);
|
|
const g_target = categoryGraph.target(gKey);
|
|
|
|
// 3. Validate the path
|
|
if (f_target !== g_source) {
|
|
throw new Error(
|
|
`Cannot compose: Target of '${fKey}' (${f_target}) does not match source of '${gKey}' (${g_source}).`
|
|
);
|
|
}
|
|
|
|
// 4. Create the composite morphism attributes
|
|
const compositeAttributes: MorphismAttributes = {
|
|
label: `${g_attributes.label} ∘ ${f_attributes.label}`,
|
|
implementation: (x) => g_attributes.implementation(f_attributes.implementation(x)),
|
|
composedOf: [fKey, gKey],
|
|
};
|
|
|
|
// 5. Add the new edge to the graph
|
|
const compositeKey = `${gKey}_o_${fKey}`;
|
|
categoryGraph.addEdgeWithKey(compositeKey, f_source, g_target, compositeAttributes);
|
|
|
|
// 6. Return the new key
|
|
console.log(`Successfully composed morphisms. New morphism created with key: '${compositeKey}'`);
|
|
return compositeKey;
|
|
}
|
|
|
|
// --- Example Usage ---
|
|
|
|
// Add Objects (Nodes)
|
|
categoryGraph.addNode('Person', { name: 'Person' });
|
|
categoryGraph.addNode('Integer', { name: 'Integer' });
|
|
categoryGraph.addNode('Boolean', { name: 'Boolean' });
|
|
|
|
// Add Base Morphisms (Edges)
|
|
categoryGraph.addEdgeWithKey('age', 'Person', 'Integer', {
|
|
label: 'Age',
|
|
implementation: (person: { name: string; age: number }) => person.age,
|
|
});
|
|
|
|
categoryGraph.addEdgeWithKey('isVoter', 'Integer', 'Boolean', {
|
|
label: 'isVoter?',
|
|
implementation: (age: number) => age >= 18,
|
|
});
|
|
|
|
console.log('Graph before composition:', categoryGraph.edges());
|
|
// Expected: ['age', 'isVoter']
|
|
|
|
// Perform composition: isVoter ∘ age
|
|
const canVoteKey = compose('age', 'isVoter');
|
|
|
|
console.log('---');
|
|
console.log('Graph after composition:', categoryGraph.edges());
|
|
// Expected: ['age', 'isVoter', 'isVoter_o_age']
|
|
console.log('---');
|
|
|
|
// Let's test our new composite morphism!
|
|
const canVoteMorphism = categoryGraph.getEdgeAttributes(canVoteKey);
|
|
|
|
const alice = { name: 'Alice', age: 30 };
|
|
const bob = { name: 'Bob', age: 16 };
|
|
|
|
console.log(`Does Alice have voting rights? ${canVoteMorphism.implementation(alice)}`); // Expected: true
|
|
console.log(`Does Bob have voting rights? ${canVoteMorphism.implementation(bob)}`); // Expected: false
|
|
|
|
// Let's test the validation by trying an invalid composition
|
|
try {
|
|
compose('isVoter', 'age');
|
|
} catch (e) {
|
|
console.error('---');
|
|
console.error(`Caught expected error: ${e.message}`);
|
|
}
|
|
```
|
|
|
|
exporting the graph via `categoryGraph.export()` shows
|
|
```typescript
|
|
{
|
|
options: { type: "mixed", multi: true, allowSelfLoops: true },
|
|
attributes: {},
|
|
nodes: [
|
|
{ key: "Person", attributes: { name: "Person" } },
|
|
{ key: "Integer", attributes: { name: "Integer" } },
|
|
{ key: "Boolean", attributes: { name: "Boolean" } }
|
|
],
|
|
edges: [
|
|
{
|
|
key: "age",
|
|
source: "Person",
|
|
target: "Integer",
|
|
attributes: { label: "Age", implementation: [Function: implementation] }
|
|
},
|
|
{
|
|
key: "isVoter",
|
|
source: "Integer",
|
|
target: "Boolean",
|
|
attributes: { label: "isVoter?", implementation: [Function: implementation] }
|
|
},
|
|
{
|
|
key: "isVoter_o_age",
|
|
source: "Person",
|
|
target: "Boolean",
|
|
attributes: {
|
|
label: "isVoter? ∘ Age",
|
|
implementation: [Function: implementation],
|
|
composedOf: [ "age", "isVoter" ]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
Having the implementations actually inside the graph makes it non-serializable but that shows the basic idea.
|
|
|