From 18b5e56ff07ec9de05b0a58424f92f2656ffb7f2 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Thu, 20 Mar 2025 12:07:31 +0100 Subject: [PATCH] add nominal types for 2D points --- generics/generics.js | 4 ++- lib/point.js | 57 ++++++++++++++++++++++++++++++++++++++ main.js | 40 +++++++++++++------------- structures/nominal_type.js | 14 ++++++++++ type_registry.js | 14 ++++++++++ util.js | 2 +- 6 files changed, 110 insertions(+), 21 deletions(-) create mode 100644 lib/point.js create mode 100644 structures/nominal_type.js diff --git a/generics/generics.js b/generics/generics.js index d17273f..f42896a 100644 --- a/generics/generics.js +++ b/generics/generics.js @@ -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}, diff --git a/lib/point.js b/lib/point.js new file mode 100644 index 0000000..3154fc9 --- /dev/null +++ b/lib/point.js @@ -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 = dθ => ({left: r, right: θ}) => { + return {left: r, right: θ+dθ}; +} + +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})})), +]}; diff --git a/main.js b/main.js index 2b8294c..a95e3c4 100644 --- a/main.js +++ b/main.js @@ -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]}, diff --git a/structures/nominal_type.js b/structures/nominal_type.js new file mode 100644 index 0000000..1b33acc --- /dev/null +++ b/structures/nominal_type.js @@ -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; +} \ No newline at end of file diff --git a/type_registry.js b/type_registry.js index ad4de51..e2c6064 100644 --- a/type_registry.js +++ b/type_registry.js @@ -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) => { diff --git a/util.js b/util.js index 9742612..6cacf99 100644 --- a/util.js +++ b/util.js @@ -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}); } \ No newline at end of file