Preliminaries
Assuming contracts from jscategory sourcecode.
log = console.log.bind(console);
logtry = (f) => {
try { log(f()) } catch (e) { log(e); }
};
undef = typeOf('undefined');
inheritsFrom = (clazz) => (c) => {
func(c);
instanceOf(clazz)(c.prototype);
return c;
};
Categories
Category = class Category {
constructor() {
let t = new.target;
if (t === Category) {
throw new TypeError('Abstract');
}
// Objects, morphisms, source, target, identity, composition
func(t.obj);
func(t.mor);
t.src = hom(t.mor, t.obj)(t.src);
t.tgt = hom(t.mor, t.obj)(t.tgt);
t.id = hom(t.obj, t.mor)(t.id);
t.com = hom(pbn([t.src, t.tgt]), t.mor)(t.com);
}
};
finnum = (n) => {
num(n);
if (!isFinite(n)) {
throw new TypeError('Expected a finite number');
}
return n;
};
// Poset of finite numbers and ≤
LTE = class LTE extends Category {
static obj(n) { return finnum(n); }
static mor(pair) {
prodn([finnum, finnum])(pair);
if (pair[0] > pair[1]) {
throw new TypeError(pair[0] + ' > ' + pair[1]);
}
return pair;
}
static src([s, t]) { return s; }
static tgt([s, t]) { return t; }
static id(n) { this.obj(n); return [n, n]; }
static com([g, f]) {
return [f[0], g[1]];
}
};
new LTE();
logtry(()=>LTE.obj(5)); // 5
logtry(()=>LTE.obj(1 / 0)); // Expected a finite number
logtry(()=>LTE.mor([1, 2])); // [1, 2]
logtry(()=>LTE.mor([1, 2, 3])); // Expected two arguments
logtry(()=>LTE.mor(['a', 1])); // Expected a number
logtry(()=>LTE.mor([1, 0])); // 1 > 0
logtry(()=>LTE.src([0, 1])); // 0
logtry(()=>LTE.tgt([0, 1])); // 1
logtry(()=>LTE.id(0)); // [0, 0]
logtry(()=>LTE.id('a')); // Expected a number.
logtry(()=>LTE.com([[2, 3], [1, 2]])); // [1, 3]
logtry(()=>LTE.com([[3, 4], [1, 2]])); // Failed to match pullback constraint.
// Monoids are one-object categories
StrCat = class StrCat extends Category {
static obj(x) { return undef(x); }
static mor(s) { return str(s); }
static src(s) { }
static tgt(s) { }
static id() { return ''; }
static com([g, f]) { return g + f; }
};
new StrCat();
logtry(()=>StrCat.com(['hi ', 'there'])); // 'hi there'
logtry(()=>StrCat.com([3, ' little pigs'])); // Expected a string
NatPlus = class NatPlus extends Category {
static obj(x) { return undef(x); }
static mor(s) { return nat32(s); }
static src(s) { }
static tgt(s) { }
static id() { return 0; }
static com([g, f]) { return g + f; }
};
new NatPlus();
positive = (n) => {
nat32(n);
if (n < 1) {
throw ('Expected a positive natural');
}
return n;
};
// In the category of matrices over ℝ,
// objects are positive natural numbers and
// morphisms m -> n are (n x m)-dimensional matrices
Matrix = class Matrix extends Category {
static obj(n) { return positive(n); }
static mor(matrix) {
arrOf(arrOf(num))(matrix);
if (matrix.length == 0 || matrix[0].length == 0) {
throw new TypeError('Malformed matrix');
}
for (let row = 1; row < matrix.length; ++row) {
if (matrix[row].length != matrix[0].length) {
throw new TypeError('Malformed matrix');
}
}
return matrix;
}
// Columns
static src(m) { return m[0].length; }
// Rows
static tgt(m) { return m.length; }
// Identity matrix
static id(n) {
let a = [];
for (let row = 0; row < n; ++row) {
a.push(Array(...Array(n)).map((_, i) => +(i == row)));
}
return a;
}
// Matrix multiplication
static com([g, f]) {
if (g[0].length != f.length) {
throw new TypeError('Not composable');
}
let a = [];
for (let i = 0; i < g.length; ++i) {
a[i] = [];
for (let k = 0; k < f[0].length; ++k) {
a[i][k] = 0;
for (let j = 0; j < f.length; ++j) {
a[i][k] += g[i][j] * f[j][k];
}
}
}
return a;
}
};
new Matrix();
logtry(()=>Matrix.id(3)); // [[1,0,0],[0,1,0],[0,0,1]]
logtry(()=>Matrix.com([[[1,2],[3,4]], [[5,6,7],[8,9,10]]])); // [[21,24,27],[47,54,61]]
Contract = class Contract extends Category {
// Objects are contracts.
static obj(c) { return func(c); }
// Morphisms are guarded functions between them.
static mor(f) {
if ((''+f).match(/\/\* guarded \*\/$/)) {
return func(f);
}
throw new TypeError('Expected a guarded function');
}
// We can't actually check src or tgt of a
// guarded function, so just return the broadest
// contract
static src(f) { return any; }
static tgt(f) { return any; }
// Identity is the identity function!
static id(c) { return c; }
// Composition is composition!
static com([g, f]) { return (x)=>g(f(x)); }
};
new Contract();
Functors
Functor = class Functor {
constructor() {
let t=new.target;
if (t === Functor) {
throw new TypeError('Abstract');
}
inheritsFrom(Category)(t.src);
inheritsFrom(Category)(t.tgt);
t.mapobj = hom(t.src.obj, t.tgt.obj)(t.mapobj);
t.mapmor = hom(t.src.mor, t.tgt.mor)(t.mapmor);
}
static map(x) {
try {
return this.mapobj(x);
} catch (_) {
return this.mapmor(x);
}
}
};
IdFunc = (cat) => {
let result = class Identity extends Functor {
static get src() { return cat; }
static get tgt() { return cat; }
static mapobj(c) { return c; }
static mapmor(f) { return f; }
};
new result();
return result;
};
IdStrCat = IdFunc(StrCat);
// IdStrCat is the identity functor on StrCat.
// It acts on objects and morphisms of StrCat.
logtry(()=>IdStrCat.map()); // undefined
logtry(()=>IdStrCat.map('foo')); // foo
StrLen = class StrLen extends Functor {
static get src() { return StrCat; }
static get tgt() { return NatPlus; }
static mapobj() { }
static mapmor(s) { return s.length; }
};
new StrLen();
logtry(()=>StrLen.map()); // undefined
logtry(()=>StrLen.map('foo')); // 3
// Category of Categories and Functors
Cat = class Cat extends Category {
static obj(c) { return inheritsFrom(Category)(c); }
static mor(f) { return inheritsFrom(Functor)(f); }
static src(f) { return f.src; }
static tgt(f) { return f.tgt; }
static id(c) { return IdFunc(c); }
static com([g, f]) {
let Composite = class Composite extends Functor {
static get src() { return f.src; }
static get tgt() { return g.tgt; }
static mapobj(c) { return g.mapobj(f.mapobj(c)); }
static mapmor(h) { return g.mapmor(f.mapmor(h)); }
};
new Composite();
return Composite;
}
};
new Cat();
logtry(()=>Cat.com([StrLen, IdStrCat]).map('foo')); // 3
ArrOf = class ArrOf extends Functor {
static get src() { return Contract; }
static get tgt() { return Contract; }
static mapobj(c) { return arrOf(c); }
static mapmor(f) { return arrOf(f); }
};
new ArrOf();
logtry(()=>ArrOf.map(int32)([1, 2, 3])); // [1, 2, 3]
logtry(()=>ArrOf.map(int32)(['a'])); // Expected a 32-bit integer
logtry(()=>ArrOf.map((x)=>x.length)(['hi', 'there'])); // [2, 5]
Natural Transformations
NatTrans = class NatTrans {
constructor() {
let t=new.target;
if (t === NatTrans) {
throw new TypeError('Abstract');
}
inheritsFrom(Functor)(t.src);
inheritsFrom(Functor)(t.tgt);
// nat trans between functors F, G: C -> D
let F = t.src;
let G = t.tgt;
if (F.src !== G.src || F.tgt !== G.tgt) {
throw new TypeError('Expected parallel functors');
}
// assigns to every object in C a morphism in D
// such that square commutes.
let C = F.src;
let D = F.tgt;
t.map = hom(C.obj, D.mor)(t.map);
}
};
// Given categories C, D, returns the functor category hom(C,D)
Hom = hom(
inheritsFrom(Category),
inheritsFrom(Category),
inheritsFrom(Category))((C, D) => {
let result = class Hom extends Category {
static obj(f) {
inheritsFrom(Functor)(f);
if (f.src !== C || f.tgt !== D) {
throw new TypeError('Wrong functor source or target');
}
return f;
}
static mor(nt) {
inheritsFrom(NatTrans)(nt);
let F = nt.src;
if (F.src !== C || F.tgt !== D) {
throw new TypeError('Wrong functor source or target');
}
return nt;
}
static src(nt) { return nt.src; }
static tgt(nt) { return nt.tgt; }
static id(f) {
let result = class IdNat extends NatTrans {
static get src() { return f; }
static get tgt() { return f; }
static map(c) { return D.id(f.mapobj(c)); }
};
new result();
return result;
}
static com([g, f]) {
let result = class Composite extends NatTrans {
static get src() { return f.src; }
static get tgt() { return g.tgt; }
static map(c) { return D.com([g.map(c), f.map(c)]); }
}
new result();
return result;
}
};
new result();
return result;
});
// Category of functors from StrCat to NatPlus
Str2Nat = Hom(StrCat, NatPlus);
// StrLen is an object in Str2Nat
logtry(()=>Str2Nat.obj(StrLen)); // StrLen
// IdStrCat goes from StrCat to itself, not to NatPlus
logtry(()=>Str2Nat.obj(IdStrCat)); // Wrong functor source or target
// Identity natural transformation on StrLen functor
IdStrLen = Str2Nat.id(StrLen);
// IdStrLen is a morphism in Str2Nat from StrLen to itself
logtry(()=>Str2Nat.mor(IdStrLen).src); // StrLen
// Category of endofunctors on Contract
End = Hom(Contract, Contract);
Flatten = class Flatten extends NatTrans {
static get src() { return Cat.com([ArrOf, ArrOf]); }
static get tgt() { return ArrOf; }
// c gets used by type checker
static map(c) {
return hom(arrOf(arrOf(c)), arrOf(c))(
(aax) => aax.reduce((prev, cur)=>prev.concat(cur)));
}
};
new Flatten();
logtry(()=>End.obj(ArrOf)); // ArrOf
logtry(()=>End.obj(Cat.com([ArrOf, ArrOf]))); // Composite
logtry(()=>End.mor(Flatten)); // Flatten
logtry(()=>Flatten.map(int32)([[1,2],[],[3,4,5]])); // [1,2,3,4,5]