add nominal types for 2D points

This commit is contained in:
Joeri Exelmans 2025-03-20 12:07:31 +01:00
parent 94efde3e65
commit 18b5e56ff0
6 changed files with 110 additions and 21 deletions

View file

@ -15,6 +15,7 @@ export const makeGeneric = callback => {
};
};
// From the given set of type variables, return only those that occur in the given type.
const occurring = (type, typeVars) => {
if (typeVars.has(type)) {
return new Set([type]);
@ -36,7 +37,8 @@ const occurring = (type, typeVars) => {
// merge {i: [1], t: List_of_Int} ->
// Thanks to Hans for pointing out that this algorithm exactly like "Unification" in Prolog:
// https://www.dai.ed.ac.uk/groups/ssp/bookpages/quickprolog/node12.html
export const matchGeneric = (
{typeVars: formalTypeVars, type: formalType},
{typeVars: actualTypeVars, type: actualType},

57
lib/point.js Normal file
View file

@ -0,0 +1,57 @@
import { Function, Type } from "../metacircular.js";
import { Double } from "../primitives/symbols.js";
import { nominalType, NominalType } from "../structures/nominal_type.js";
import { fnType, prodType, typedFnType } from "../type_registry.js";
const PointCartesian2D = nominalType(
// just a unique number, to make sure that our type is only equal to itself
BigInt("0xBBAAD62B10EE21993BA690A732DA2A6875CE4B6F5E7139D5AEC9FD887F9D24A8"))
(prodType(Double, Double));
const PointPolar2D = nominalType(
// just a unique number, to make sure that our type is only equal to itself
BigInt("0x31CDAB4B3D84C4EB27D3C111FD7580E533268B72E05BD694F8B262913E018B72"))
(prodType(Double, Double));
export const cart2polar = ({left: x, right: y}) => {
const r = Math.sqrt(x*x + y*y);
const θ = Math.atan(y/x);
return {left: r, right: θ};
};
export const polar2cart = ({left: r, right: θ}) => {
const x = r * Math.cos(θ);
const y = r * Math.sin(θ);
return {left: x, right: y};
}
export const translate = dx => dy => ({left: x, right: y}) => {
return {left: x+dx, right: y+dy};
}
export const rotate = => ({left: r, right: θ}) => {
return {left: r, right: θ+};
}
export const scale = dr => ({left: r, right: θ}) => {
return {left: r+dr, right: θ};
}
export const ModulePoint = {l:[
{i: -1 , t: Double},
{i: 2 , t: Double},
{i: {left: 1, right: 2}, t: PointCartesian2D},
{i: PointCartesian2D , t: NominalType},
{i: PointPolar2D , t: NominalType},
...typedFnType(cart2polar, fnType => fnType({in: PointCartesian2D, out: PointPolar2D})),
...typedFnType(polar2cart, fnType => fnType({in: PointPolar2D, out: PointCartesian2D})),
...typedFnType(translate , fnType => fnType({in: Double, out: fnType({in: Double, out: fnType({in: PointCartesian2D, out: PointCartesian2D})})})),
...typedFnType(rotate, fnType => fnType({in: Double, out: fnType({in: PointPolar2D, out: PointPolar2D})})),
...typedFnType(scale, fnType => fnType({in: Double, out: fnType({in: PointPolar2D, out: PointPolar2D})})),
]};

40
main.js
View file

@ -100,29 +100,31 @@ const ModuleValues = {l:[
{i: {variant: "L", value: 100n}, t: sumType(Int, Bool)},
]};
import { select } from '@inquirer/prompts';
import { ModulePoint } from "./lib/point.js";
const ctx = new Context({l:[
...ModuleMetaCircular.l,
...ModuleTyped.l,
// ...ModuleConformable,
// ...ModuleConformanceCheckConforms,
// ...ModuleNum,
// ...ModuleEq,
...ModuleBool.l,
...ModuleInt.l,
...ModuleDouble.l,
// ...ModuleSquare,
// ...ModuleList,
...makeIdFn(Int).l,
...makeSquare({i: mulInt, t: Int_to_Int_to_Int}).l,
...makeSquare({i: mulDouble, t: Double_to_Double_to_Double}).l,
...ModuleList.l,
...ModuleValues.l,
...ModuleModule.l,
// ...ModuleMetaCircular.l,
// ...ModuleTyped.l,
// // ...ModuleConformable,
// // ...ModuleConformanceCheckConforms,
// // ...ModuleNum,
// // ...ModuleEq,
// ...ModuleBool.l,
// ...ModuleInt.l,
// ...ModuleDouble.l,
// // ...ModuleSquare,
// // ...ModuleList,
// ...makeIdFn(Int).l,
// ...makeSquare({i: mulInt, t: Int_to_Int_to_Int}).l,
// ...makeSquare({i: mulDouble, t: Double_to_Double_to_Double}).l,
// ...ModuleList.l,
// ...ModuleValues.l,
// ...ModuleModule.l,
...ModulePoint.l,
]});
import { input, select } from '@inquirer/prompts';
const makeChoice = ([i, t]) => {
return {
value: {i, t: t[0]},

View file

@ -0,0 +1,14 @@
import { Type } from "../metacircular.js";
import { Int } from "../primitives/symbols.js";
import { prodType } from "../type_registry.js";
import { makeProductType } from "./product.js";
export const NominalType = prodType(Int, Type);
export const ModuleNominalType = makeProductType(Int, Type);
export const nominalType = x => {
const result = ModuleNominalType.l[5].i(x); // dirty!!
result.__proto__.toString = () => "HEY";
return result;
}

View file

@ -2,6 +2,7 @@
// Maybe we shouldn't use Symbols for types, but we also cannot use primitive values for types because they may accidentally overlap with 'real' values (that are not types)
// We also cannot use objects for types because similar to Symbols, objects are only equal to themselves if we use them as Map keys.
import { Function } from "./metacircular.js";
import { DefaultMap } from "./util.js";
// Global store of function types:
@ -18,6 +19,19 @@ export const fnType = ({in: inType, out: outType}) => {
}
};
export const typedFnType = (instance, callback) => {
const fnTs = [];
const wrappedFnType = ({in: inType, out: outType}) => {
const fnT = fnType({in: inType, out: outType});
fnTs.push(fnT);
return fnT;
}
return [
{i: instance, t: callback(wrappedFnType)},
...fnTs.map(fnT => ({i: fnT, t: Function})),
];
}
// Global store of list types:
const listTypes = new Map();
export const lsType = (elementType) => {

View file

@ -45,5 +45,5 @@ export class DefaultMap {
import { inspect } from 'node:util';
export function pretty(obj) {
return inspect(obj, {colors: true});
return inspect(obj, {colors: true, depth: null});
}