type inferencing "unifying" operation is now bi-directional + begin writing generic version of "unifying" operation (that should work on all types)
This commit is contained in:
parent
c5ac55b0ff
commit
33c156fc5c
5 changed files with 148 additions and 9 deletions
|
|
@ -48,6 +48,75 @@ export const occurring = (type, typeVars) => {
|
||||||
return new Set();
|
return new Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const properUnify = eqDict => (
|
||||||
|
{typeVars: formalTypeVars, type: formalType},
|
||||||
|
{typeVars: actualTypeVars, type: actualType},
|
||||||
|
) => {
|
||||||
|
if (getEq(eqDict)(formalType)(actualType)) {
|
||||||
|
return {
|
||||||
|
substitutions: new Map(),
|
||||||
|
typeVars: new Set([
|
||||||
|
...actualTypeVars,
|
||||||
|
// ...formalTypeVars, // <- i don't think we need these?
|
||||||
|
]),
|
||||||
|
type: actualType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formalTypeVars.has(formalType)) {
|
||||||
|
// formalType is type variable -> substitute it by actualType
|
||||||
|
return {
|
||||||
|
substitutions: new Map([[formalType, actualType]]),
|
||||||
|
typeVars: new Set([
|
||||||
|
...actualTypeVars,
|
||||||
|
...formalTypeVars,
|
||||||
|
].filter(a => a !== formalType)),
|
||||||
|
type: actualType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (actualTypeVars.has(actualType)) {
|
||||||
|
// same as above, but in opposite direction:
|
||||||
|
// actualType is type variable -> substitute it by formalType
|
||||||
|
return {
|
||||||
|
substitutions: new Map([[actualType, formalType]]),
|
||||||
|
typeVars: new Set([
|
||||||
|
...actualTypeVars,
|
||||||
|
...formalTypeVars,
|
||||||
|
].filter(a => a !== actualType)),
|
||||||
|
type: formalType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WIP...
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergeOneWay = (m1, m2) => {
|
||||||
|
const m1copy = new Map(m1);
|
||||||
|
const m2copy = new Map(m2);
|
||||||
|
for (const [key1, val1] of m1copy.entries()) {
|
||||||
|
if (m2copy.has(val1)) {
|
||||||
|
m1copy.set(key1, m2.get(val1));
|
||||||
|
m2copy.delete(val1);
|
||||||
|
return [false, m1copy, m2copy, new Set([val1])];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [true, m1copy, m2copy, new Set()]; // stable
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mergeSubstitutions = (m1, m2) => {
|
||||||
|
let stable = false;
|
||||||
|
let deletedTypeVars = new Set();
|
||||||
|
while (!stable) {
|
||||||
|
let d;
|
||||||
|
// notice we swap m2 and m1, so the rewriting can happen both ways:
|
||||||
|
[stable, m2, m1, d] = mergeOneWay(m1, m2);
|
||||||
|
if (!stable) {
|
||||||
|
deletedTypeVars = deletedTypeVars.union(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [new Map([...m1, ...m2]), deletedTypeVars];
|
||||||
|
}
|
||||||
|
|
||||||
// Currently very ad-hoc.
|
// Currently very ad-hoc.
|
||||||
|
|
||||||
// Thanks to Hans for pointing out that this algorithm exactly like "Unification" in Prolog (hence the function name):
|
// Thanks to Hans for pointing out that this algorithm exactly like "Unification" in Prolog (hence the function name):
|
||||||
|
|
@ -77,6 +146,17 @@ export const unify = (
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (actualTypeVars.has(actualType)) {
|
||||||
|
// same as above, but in the other direction
|
||||||
|
return {
|
||||||
|
substitutions: new Map([[actualType, formalType]]),
|
||||||
|
typeVars: new Set([
|
||||||
|
...actualTypeVars,
|
||||||
|
...formalTypeVars].filter(a => a !== actualType)),
|
||||||
|
type: formalType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (formalType.in !== undefined) {
|
if (formalType.in !== undefined) {
|
||||||
// function type
|
// function type
|
||||||
if (actualType.in === undefined) {
|
if (actualType.in === undefined) {
|
||||||
|
|
@ -95,13 +175,15 @@ export const unify = (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// merge substitutions
|
// merge substitutions
|
||||||
const newSubstitutions = new Map([
|
const [newSubstitutions, deletedTypeVars] = mergeSubstitutions(
|
||||||
...inType.substitutions,
|
inType.substitutions, outType.substitutions);
|
||||||
...outType.substitutions,
|
// const newSubstitutions = new Map([
|
||||||
]);
|
// ...inType.substitutions,
|
||||||
|
// ...outType.substitutions,
|
||||||
|
// ]);
|
||||||
const newTypeVars = new Set([
|
const newTypeVars = new Set([
|
||||||
...actualTypeVars,
|
...actualTypeVars,
|
||||||
...formalTypeVars].filter(a => !newSubstitutions.has(a)));
|
...formalTypeVars].filter(a => !newSubstitutions.has(a) && !deletedTypeVars.has(a)));
|
||||||
return {
|
return {
|
||||||
substitutions: newSubstitutions,
|
substitutions: newSubstitutions,
|
||||||
typeVars: newTypeVars,
|
typeVars: newTypeVars,
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
import { Bool, Int } from "../primitives/symbols.js";
|
import { Bool, Int } from "../primitives/symbols.js";
|
||||||
import { lsType } from "../structures/list_common.js";
|
import { lsType } from "../structures/list_common.js";
|
||||||
import { fnType } from "../metacircular.js";
|
import { fnType } from "../metacircular.js";
|
||||||
import { assign, makeGeneric, unify } from "./generics.js";
|
import { assign, makeGeneric, mergeSubstitutions, unify } from "./generics.js";
|
||||||
|
import { select } from "@inquirer/prompts";
|
||||||
|
|
||||||
// a -> Int
|
// a -> Int
|
||||||
const a_to_Int = makeGeneric(a => fnType({in: a, out: Int}));
|
const a_to_Int = makeGeneric(a => fnType({in: a, out: Int}));
|
||||||
// Bool -> Int
|
// Bool -> Int
|
||||||
const Bool_to_Int = makeGeneric(() => fnType({in: lsType(Bool), out: Int}));
|
const Bool_to_Int = makeGeneric(() => fnType({in: lsType(Bool), out: Int}));
|
||||||
// should be: Bool -> Int
|
console.log("should be: Bool -> Int")
|
||||||
console.log(unify(a_to_Int, Bool_to_Int));
|
console.log(unify(a_to_Int, Bool_to_Int));
|
||||||
|
|
||||||
// (a -> a) -> b
|
// (a -> a) -> b
|
||||||
const fnType2 = makeGeneric((a,b) => fnType({in: fnType({in: a, out: a}), out: b}));
|
const fnType2 = makeGeneric((a,b) => fnType({in: fnType({in: a, out: a}), out: b}));
|
||||||
// (Bool -> Bool) -> a
|
// (Bool -> Bool) -> a
|
||||||
const fnType3 = makeGeneric(a => fnType({in: fnType({in: Bool, out: Bool}), out: a}));
|
const fnType3 = makeGeneric(a => fnType({in: fnType({in: Bool, out: Bool}), out: a}));
|
||||||
// should be: (Bool -> Bool) -> a
|
console.log("should be: (Bool -> Bool) -> a");
|
||||||
console.log(unify(fnType2, fnType3));
|
console.log(unify(fnType2, fnType3));
|
||||||
|
|
||||||
// (a -> b) -> [a] -> [b]
|
// (a -> b) -> [a] -> [b]
|
||||||
|
|
@ -26,5 +27,14 @@ const mapFnType = makeGeneric((a,b) =>
|
||||||
// a -> a
|
// a -> a
|
||||||
const idFnType = makeGeneric(a =>
|
const idFnType = makeGeneric(a =>
|
||||||
fnType({in: a, out: a}));
|
fnType({in: a, out: a}));
|
||||||
// should be: [a] -> [a]
|
console.log("should be: [a] -> [a]");
|
||||||
console.log(assign(mapFnType, idFnType));
|
console.log(assign(mapFnType, idFnType));
|
||||||
|
|
||||||
|
// (a -> Int) -> [a] -> a
|
||||||
|
const weirdFnType = makeGeneric(a =>
|
||||||
|
fnType({
|
||||||
|
in: fnType({in: a, out: Int}),
|
||||||
|
out: fnType({in: lsType(a), out: a}),
|
||||||
|
}));
|
||||||
|
console.log("should be: [Int] -> Int");
|
||||||
|
console.log(assign(weirdFnType, idFnType));
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { makeGeneric } from "../generics/generics";
|
||||||
|
import { Type, typedFnType } from "../metacircular";
|
||||||
|
import { Bool, Byte, Char, Double, Int } from "../primitives/symbols";
|
||||||
|
import { deepEqual } from "../util";
|
||||||
|
import { eqDictType } from "./eq_type";
|
||||||
|
|
||||||
|
export const getEq = numDict => numDict.eq;
|
||||||
|
|
||||||
|
export const ModuleEq = {l:[
|
||||||
|
...typedFnType(eqDictType, fnType => fnType({in: Type, out: Type})),
|
||||||
|
|
||||||
|
...typedFnType(getEq, fnType => makeGeneric(a =>
|
||||||
|
fnType({
|
||||||
|
in: eqDictType(a),
|
||||||
|
out: fnType({
|
||||||
|
in: a,
|
||||||
|
out: fnType({
|
||||||
|
in: a,
|
||||||
|
out: Bool,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}))),
|
||||||
|
]};
|
||||||
|
|
||||||
|
// all our data (and types) are encoded such that we can test equality the same way:
|
||||||
|
|
||||||
|
const eq = x => y => deepEqual(x,y);
|
||||||
|
|
||||||
|
const EqDict = {eq};
|
||||||
|
|
||||||
|
export const EqInstances = new Map([
|
||||||
|
[Int, EqDict],
|
||||||
|
[Bool, EqDict],
|
||||||
|
[Double, EqDict],
|
||||||
|
[Byte, EqDict],
|
||||||
|
[Char, EqDict],
|
||||||
|
[Type, EqDict],
|
||||||
|
]);
|
||||||
4
typeclasses/eq_type.js
Normal file
4
typeclasses/eq_type.js
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import { DefaultMap } from "../util.js";
|
||||||
|
|
||||||
|
const eqDictTypeRegistry = new DefaultMap(a => ({ eqDict: a }));
|
||||||
|
export const eqDictType = a => eqDictTypeRegistry.getdefault(a, true);
|
||||||
|
|
@ -2,6 +2,7 @@ import { assign } from "../generics/generics.js";
|
||||||
import { makeGeneric } from "../generics/generics.js";
|
import { makeGeneric } from "../generics/generics.js";
|
||||||
import { fnType } from "../metacircular.js";
|
import { fnType } from "../metacircular.js";
|
||||||
import { Double, Int } from "../primitives/symbols.js";
|
import { Double, Int } from "../primitives/symbols.js";
|
||||||
|
import { getMul, NumInstances } from "./num.js";
|
||||||
import { numDictType } from "./num_type.js";
|
import { numDictType } from "./num_type.js";
|
||||||
|
|
||||||
const square = numDict => x => getMul(numDict)(x)(x);
|
const square = numDict => x => getMul(numDict)(x)(x);
|
||||||
|
|
@ -21,3 +22,7 @@ console.log(assign(squareFnType, makeGeneric(() => numDictType(Int))));
|
||||||
// should be: Double -> Double
|
// should be: Double -> Double
|
||||||
console.log(assign(squareFnType, makeGeneric(() => numDictType(Double))));
|
console.log(assign(squareFnType, makeGeneric(() => numDictType(Double))));
|
||||||
|
|
||||||
|
// to call 'square' we need:
|
||||||
|
// - access to a mapping from types to their typeclass instantiation
|
||||||
|
// - to know that our argument is 'Int'
|
||||||
|
console.log(square(NumInstances.get(Int))(42n)); // 1764n
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue