Published
Edited
Jun 27, 2022
Importers
1 star
Insert cell
md`# Algebraic data types, optimized 29 April 2022 `
Insert cell
Insert cell
Insert cell
ADT = {
const makeADT = (...args) => {
if (typeof args[0] === 'string' && Array.isArray(args[1])) {
return makeProduct(...args);
}
if (Array.isArray(args[0])) {
return makeSum(...args);
}
throw new Error('Must call ADT(<string>, [<ADT types>]) or ADT([<ADT types>])');
};

return Object.assign(makeADT, {
Sum: makeSum,
Product: makeProduct
});
}
Insert cell
function makeSum(variantsOrSumType) {
let sumType = null;
if (Array.isArray(variantsOrSumType)) {
const variantConstructors = variantsOrSumType.reduce((variantConstructors, variant) => {
if (variant.adtType === 'product') {
variantConstructors[variant.name] = variant;
} else {
Object.assign(variantConstructors, variant);
}
return variantConstructors;
}, {});
sumType = {
...variantConstructors,
};
} else {
sumType = {
...variantsOrSumType
};
}

const variants = Object.values(sumType);

const match = typeOps.match(variants);
Object.defineProperties(sumType, {
adtType: { enumerable: false
, value: 'sum' },
variants: { enumerable: false
, value: variants },
match: { enumerable: false
, value: match },
unsafeFast_match: { enumerable: false
, value: (matchObj) => (adtInstance) => matchObj[adtInstance.tag](adtInstance) },
withMethod: { enumerable: false
, value: (methodName, arityOrParams, methodF) => {
const sumTypeWithMethod = mapObj(
prodType => methodName in prodType.methods
? prodType
: prodType.withMethod(methodName, arityOrParams, methodF)
)(sumType);
return makeSum(sumTypeWithMethod);
}
},
withMethodRec: { enumerable: false
, value: (methodName, arityOrParams, typeToMethodF) => {
const sumTypeWithMethod = mapObj(
prodType => methodName in prodType.methods
? prodType
: prodType.withMethodRec(methodName, arityOrParams, typeToMethodF)
)(sumType);
return makeSum(sumTypeWithMethod);
}},
withMethods: { enumerable: false
, value: (methodsObj) => {
let finalSumType = sumType;
Object.entries(methodsObj).forEach(([methodName, {arity, parameters, method}]) => {
finalSumType = finalSumType.withMethod(methodName, parameters ?? arity, method);
});
return finalSumType;
}
},
withMethodsRec: { enumerable: false
, value: (objOfTypesToMethods) => {
let finalSumType = sumType;
Object.entries(objOfTypesToMethods)
.forEach(([methodName, {arity, parameters, method: typeToMethodF}]) => {
finalSumType = finalSumType.withMethodRec(methodName, parameters ?? arity, typeToMethodF);
});
return finalSumType;
}}
});

Object.assign(sumType, mapObj(variant => variant.associateSumType(sumType))(sumType));

return Object.freeze(sumType);
}
Insert cell
function makeProduct (name, fields = [], associatedSumType = makeSum([]), methods = {}) {
const construct = (...args) => {
const constructedObj = typeof args[0] === 'object' &&
fieldsListsEqual(Object.keys(args[0]), fields)
? args[0]
: fields.reduce((prod, field, i) => {
prod[field] = args[i];
return prod;
}, {});
let instance = {
tag: name,
...constructedObj
};

let methodDescriptors = {};
for (let methodName in methods) {
const methodDesc = methods[methodName];
const method = methodDesc.method(associatedSumType);

const finalMethodForArity = {
0: () =>
method(instance),
1: (...args0) =>
method(...args0)(instance),
2: (...args0) => (...args1) =>
method(...args0)(...args1)(instance),
3: (...args0) => (...args1) => (...args2) =>
method(...args0)(...args1)(...args2)(instance),
4: (...args0) => (...args1) => (...args2) => (...args3) =>
method(...args0)(...args1)(...args2)(...args3)(instance),
5: (...args0) => (...args1) => (...args2) => (...args3) => (...args4) =>
method(...args0)(...args1)(...args2)(...args3)(...args4)(instance)
};

const params = methodDesc.arityOrParams;
const finalMethodForParams = {
0: () =>
`() => method(instance)`,
1: () =>
`(...${params[0]}) => method(...${params[0]})(instance)`,
2: () =>
`(...${params[0]}) => (...${params[1]}) => method(...${params[0]})(...${params[1]})(instance)`,
3: () =>
`(...${params[0]}) => (...${params[1]}) => (...${params[2]}) => method(...${params[0]})(...${params[1]})(...${params[2]})(instance)`,
4: () =>
`(...${params[0]}) => (...${params[1]}) => (...${params[2]}) => (...${params[3]}) => method(...${params[0]})(...${params[1]})(...${params[2]})(...${params[3]})(instance)`,
5: () =>
`(...${params[0]}) => (...${params[1]}) => (...${params[2]}) => (...${params[3]}) => (...${params[4]}) => method(...${params[0]})(...${params[1]})(...${params[2]})(...${params[3]})(...${params[4]})(instance)`
};
methodDescriptors[methodName] = {
enumerable: false,
value: typeof methodDesc.arityOrParams === 'number'
? finalMethodForArity[methodDesc.arityOrParams]
: eval(finalMethodForParams[methodDesc.arityOrParams.length]())
};
}

const match = typeOps.match(associatedSumType.variants);
console.log(methodDescriptors);
Object.defineProperties(instance, {
match: { enumerable: false,
value: (matchObj) => {

return match(matchObj)(instance);
}
},
unsafeFast_match: { enumerable: false,
value: (matchObj) => {
return matchObj[instance.tag](instance)
}},
variants: { enumerable: false,
value: associatedSumType.variants},
instanceId: { enumerable: false,
value: DOM.uid('instance_of_product_type_' + name)},
...methodDescriptors
});

return Object.freeze(instance);
};

const makeProductInstance = construct;

Object.defineProperty(makeProductInstance, "name", { value: name });

Object.defineProperties(
makeProductInstance
, { adtType: { enumerable: false
, value: 'product' }
, associateSumType: { enumerable: false
, value: (sumType) => makeProduct(name, fields, sumType, methods) }
, withMethod: { enumerable: false
, value: (methodName, arityOrParams, methodF) =>
makeProduct(name,
fields,
associatedSumType,
{ ...methods
, [methodName]: { method: Algebra => methodF
, arityOrParams }})}
, withMethodRec: { enumerable: false
, value: (methodName, arityOrParams, typeToMethodF) =>
makeProduct(name,
fields,
associatedSumType,
{ ...methods
, [methodName]: { method: typeToMethodF
, arityOrParams}})
}
, methods: { enumerable: false
, value: methods }
, fields: { enumerable: false
, value: fields }
});

return Object.freeze(makeProductInstance);
}
Insert cell
Insert cell
typeOps = ({
match: (variants) => {
const variantNames = variants.reduce((variantNames, v) => {
return v.adtType === 'product'
? [...variantNames, v.name]
: [...variantNames, ...Object.keys(v)]
}, []);

return (matchObj) => {
if (variantNames.some(name => name in matchObj === false)) {
const fieldToProperty = (field) =>
field.match(/^[a-z0-9]+$/i) // is alphanumeric
? `${field}`
: `'${field}'`;

const fieldsForVariant = (variantName) =>
_.find(variants, {name: variantName}).fields.join(', ');

const longestVariantName = fieldToProperty(
_.maxBy(variants, variant => variant.name.length).name);
const spacesFor = (variantName) =>
_.repeat(' ', longestVariantName.length - fieldToProperty(variantName).length);

throw new Error(
`Match object must provide a case for each variant of ${variantNames.join(', ')}: \n` +
'{\n' + variantNames.map(variant => ` ${fieldToProperty(variant)}${spacesFor(variant)}: ({${fieldsForVariant(variant)}}) => {}`).join(',\n') + '\n}'
);
}
return (prodInstance) => {
return matchObj[prodInstance.tag](prodInstance);
}
}
}
})
Insert cell
ADT.Product('Empty')()
Insert cell
Branch = ADT.Product('Branch', ['left', 'right'])

Insert cell
TT = ADT.Sum([
ADT.Product('Branch', ['left', 'right'])
.withMethod('show', 0, branch => `-left ${branch.left.show()} right ${branch.right.show()}`),
ADT.Product('Leaf', ['value'])
// .withMethod('show', 0, leaf => `-<${leaf.value}>`),
])
.withMethods({
'show': { arity: 0
, method: tree => tree.match({
Leaf: ({value}) => `+<${value}>`
})}
})

Insert cell
// TT.Branch(TT.Leaf(3), TT.Leaf(5)).show(3)
Insert cell
show = {
const show = TT.match({
Branch: ({left, right}) => (indent) =>
`${indent}Branch(\n` +
`${indent}${show(left)(indent + ' ')}, \n` +
`${indent}${show(right)(indent + ' ')}\n` +
`${indent})`,
Leaf: ({value}) => (indent) => `${indent}Leaf(${value})`
});
return (tt) => show(tt)('');
}
Insert cell
show(
TT.Branch(
TT.Branch(
TT.Leaf(1),
TT.Branch(
TT.Leaf('a'),
TT.Leaf('b')
)
),
TT.Leaf(3)))
Insert cell
TestSum = {
const TestSum = ADT.Sum([
ADT.Product('variant1', ['value']),
ADT.Product('variant2', ['value', 'value2'])
])
.withMethod('getPoints2', 0, sum => 2)
.withMethodRec('translate', ['byPoint'], Algebra => byPoint => sum => { return Algebra.variant1(1); })
.withMethodRec('test', 0, Algebra => sum => sum.getPoints())
.withMethod('getPoints', 0, sum => 2);

return TestSum;
}
Insert cell
TestSum.variant1('a').translate([1,2])
Insert cell
Insert cell
Insert cell
Insert cell
R = require('ramda')
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more