# 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(); // --- 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.