Skip to content

Commit

Permalink
Merge pull request #78 from sanctuary-js/dc-perf
Browse files Browse the repository at this point in the history
improve performance of _determineActualTypes
  • Loading branch information
davidchambers authored Jul 4, 2016
2 parents 8835e65 + 2f94dbc commit 0b4db18
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 106 deletions.
144 changes: 38 additions & 106 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -762,85 +762,15 @@
}
};

// unexpectedType :: Any -> TypeError
var unexpectedType = /* istanbul ignore next */ function(x) {
return new TypeError(
'Unexpected type ' +
LEFT_SINGLE_QUOTATION_MARK + x + RIGHT_SINGLE_QUOTATION_MARK
);
};

// equalTypes :: (Type, Type, Boolean) -> Boolean
var equalTypes = function equalTypes(t1, t2, loose) {
if (t1.type === 'INCONSISTENT' || t2.type === 'INCONSISTENT') return loose;
if (t1.type === 'UNKNOWN' || t2.type === 'UNKNOWN') return true;
switch (t1.type) {
case 'NULLARY':
return t1.type === t2.type && t1.name === t2.name;
case 'UNARY':
return t1.type === t2.type && t1.name === t2.name &&
equalTypes(t1.$1, t2.$1, loose);
case 'BINARY':
return t1.type === t2.type && t1.name === t2.name &&
equalTypes(t1.$1, t2.$1, loose) &&
equalTypes(t1.$2, t2.$2, loose);
case 'ENUM':
return t1.type === t2.type && show(t1) === show(t2);
case 'RECORD':
return t1.type === t2.type && show(t1) === show(t2);
/* istanbul ignore next */
default:
throw unexpectedType(t1.type);
}
};

// chooseType :: (Type, Type) -> Type
var chooseType = function(t1, t2) {
return t1.type === 'UNKNOWN' ? t2 : t1;
};

// mergeTypes :: (Type, Type) -> Type
//
// Either String ??? `mergeTypes` Either ??? Number = Either String Number
var mergeTypes = function(t1, t2) {
return (
t1.type === 'UNARY' ?
UnaryType.from(t1)(chooseType(t1.$1, t2.$1)) :
t1.type === 'BINARY' ?
BinaryType.from(t1)(chooseType(t1.$1, t2.$1),
chooseType(t1.$2, t2.$2)) :
// else
chooseType(t1, t2)
);
};

// commonTypes :: ([[Type]], Boolean) -> [Type]
//
// [[String, RegexFlags], [String, RegexFlags]] ---> [String, RegexFlags]
//
// [[Boolean], [Boolean], [Boolean], [Number]] ---> []
//
// [[Array ???], [Array String], [Array ???]] ---> [Array String]
//
// [[Either String ???], [Either ??? Number]] ---> [Either String Number]
var commonTypes = function(typeses, loose) {
return reduce(typeses[0], [], function(types, t) {
var st = reduce(typeses, {ok: true, type: t}, function(st, types) {
var st$ = reduce(types, {ok: false, type: st.type}, function(st, t) {
var equal = equalTypes(st.type, t, loose);
return {ok: equal || st.ok,
type: equal ? mergeTypes(st.type, t) : st.type};
});
return {type: st$.type, ok: st.ok && st$.ok};
});
return st.ok ? types.concat([st.type]) : types;
});
};

// _determineActualTypes :: (Boolean, [Type], [Object], [Any]) -> [Type]
var _determineActualTypes = function recur(loose, env, seen, values) {
// typeses :: [[Type]]
var typeses = map(values, function(value) {
// _determineActualTypes :: ... -> [Type]
var _determineActualTypes = function recur(
loose, // :: Boolean
env, // :: [Type]
types, // :: [Type]
seen, // :: [Object]
values // :: [Any]
) {
var refine = function(types, value) {
var seen$;
if (typeof value === 'object' && value != null ||
typeof value === 'function') {
Expand All @@ -851,24 +781,30 @@
} else {
seen$ = seen;
}
return chain(env, function(t) {
return chain(types, function(t) {
return (
t.name === 'sanctuary-def/Nullable' || !t._test(value) ?
[] :
t.type === 'UNARY' ?
map(recur(loose, env, seen$, t._1(value)), UnaryType.from(t)) :
map(recur(loose, env, env, seen$, t._1(value)),
UnaryType.from(t)) :
t.type === 'BINARY' ?
BinaryType.xprod(t,
recur(loose, env, seen$, t._1(value)),
recur(loose, env, seen$, t._2(value))) :
t.$1.type === 'UNKNOWN' ?
recur(loose, env, env, seen$, t._1(value)) :
[t.$1],
t.$2.type === 'UNKNOWN' ?
recur(loose, env, env, seen$, t._2(value)) :
[t.$2]) :
// else
[t]
);
});
});
};

return isEmpty(values) ?
[Unknown] :
or(commonTypes(typeses, loose), [Inconsistent]);
or(reduce(values, types, refine), loose ? [Inconsistent] : []);
};

// rejectInconsistent :: [Type] -> [Type]
Expand All @@ -878,20 +814,22 @@
});
};

// determineActualTypesStrict :: ([Type], [Any]) -> [Type]
var determineActualTypesStrict = function(env, values) {
return rejectInconsistent(_determineActualTypes(false, env, [], values));
// determineActualTypesStrict :: ([Type], [Type], [Any]) -> [Type]
var determineActualTypesStrict = function(env, types, values) {
var types$ = _determineActualTypes(false, env, types, [], values);
return rejectInconsistent(types$);
};

// determineActualTypesLoose :: ([Type], [Any]) -> [Type]
var determineActualTypesLoose = function(env, values) {
return rejectInconsistent(_determineActualTypes(true, env, [], values));
// determineActualTypesLoose :: ([Type], [Type], [Any]) -> [Type]
var determineActualTypesLoose = function(env, types, values) {
var types$ = _determineActualTypes(true, env, types, [], values);
return rejectInconsistent(types$);
};

// valuesToPairs :: ([Type], [Any]) -> [Pair Any [Type]]
var valuesToPairs = function(env, values) {
return map(values, function(x) {
return [x, determineActualTypesLoose(env, [x])];
return [x, determineActualTypesLoose(env, env, [x])];
});
};

Expand Down Expand Up @@ -942,8 +880,11 @@
}
}
if (has(typeVarName, typeVarMap)) {
okTypes = filterTypesByValues(typeVarMap[typeVarName].types,
values);
okTypes = _determineActualTypes(false,
env,
typeVarMap[typeVarName].types,
[],
values);
if (isEmpty(okTypes)) {
return Left(typeVarConstraintViolation2(
name,
Expand All @@ -958,7 +899,7 @@
));
}
} else {
okTypes = determineActualTypesStrict(env, values);
okTypes = determineActualTypesStrict(env, env, values);
if (isEmpty(okTypes) && !isEmpty(values)) {
return Left(typeVarConstraintViolation(
name,
Expand Down Expand Up @@ -1032,7 +973,7 @@

default:
return Right({typeVarMap: typeVarMap,
types: determineActualTypesStrict(env, values)});
types: determineActualTypesStrict(env, env, values)});
}
};
};
Expand Down Expand Up @@ -1064,21 +1005,12 @@
};

// test :: ([Type], Type, Any) -> Boolean
var test = $.test = function(_env, t, x) {
$.test = function(_env, t, x) {
var env = applyParameterizedTypes(_env);
var f = _satisfactoryTypes(env, 'name', {}, [t], 0);
return f({}, t, [x], [], []).isRight;
};

// filterTypesByValues :: ([Type], [Any]) -> [Type]
var filterTypesByValues = function(env, values) {
return filter(env, function(t) {
return all(values, function(x) {
return test(env, t, x);
});
});
};

// invalidArgumentsLength :: (String, Integer, Integer) -> Error
var invalidArgumentsLength = function(name, expectedLength, actualLength) {
return new TypeError(
Expand Down
99 changes: 99 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,33 @@ describe('def', function() {
eq(length(vm.runInNewContext('["foo", "bar", "baz"]')), 3);
});

it('accommodates circular references', function() {
// id :: a -> a
var id = def('id', {}, [a, a], R.identity);

var x = {name: 'x'};
var y = {name: 'y'};
x.y = y;
y.x = x;

eq(id(x), x);

var z = [];
z.push(z);

throws(function() { id(z); },
errorEq(TypeError,
'Type-variable constraint violation\n' +
'\n' +
'id :: a -> a\n' +
' ^\n' +
' 1\n' +
'\n' +
'1) [<Circular>] :: Array ???\n' +
'\n' +
'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n'));
});

it('supports custom types', function() {
// AnonJust :: a -> AnonMaybe a
var AnonJust = function(x) {
Expand Down Expand Up @@ -1711,6 +1738,65 @@ describe('def', function() {
'2) [1, 2, 3] :: Array Number\n' +
'\n' +
'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n'));

// f :: a -> a -> a -> a
var f = def('f', {}, [a, a, a, a], function(x, y, z) { return x; });

throws(function() { f(Left('abc'), Left(/XXX/)); },
errorEq(TypeError,
'Type-variable constraint violation\n' +
'\n' +
'f :: a -> a -> a -> a\n' +
' ^ ^\n' +
' 1 2\n' +
'\n' +
'1) Left("abc") :: Either String ???\n' +
'\n' +
'2) Left(/XXX/) :: Either RegExp ???\n' +
'\n' +
'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n'));

throws(function() { f(Right(123), Right(/XXX/)); },
errorEq(TypeError,
'Type-variable constraint violation\n' +
'\n' +
'f :: a -> a -> a -> a\n' +
' ^ ^\n' +
' 1 2\n' +
'\n' +
'1) Right(123) :: Either ??? Number\n' +
'\n' +
'2) Right(/XXX/) :: Either ??? RegExp\n' +
'\n' +
'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n'));

// throws(function() { f(Left('abc'), Right(123), Left(/XXX/)); },
// errorEq(TypeError,
// 'Type-variable constraint violation\n' +
// '\n' +
// 'f :: a -> a -> a -> a\n' +
// ' ^ ^\n' +
// ' 1 2\n' +
// '\n' +
// '1) Left("abc") :: Either String ???\n' +
// '\n' +
// '2) Left(/XXX/) :: Either RegExp ???\n' +
// '\n' +
// 'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n'));

throws(function() { f(Left('abc'), Right(123), Right(/XXX/)); },
errorEq(TypeError,
'Type-variable constraint violation\n' +
'\n' +
'f :: a -> a -> a -> a\n' +
' ^ ^\n' +
' 1 2\n' +
'\n' +
'1) Right(123) :: Either ??? Number\n' +
'\n' +
'2) Right(/XXX/) :: Either ??? RegExp\n' +
'\n' +
'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n'));
});

it('supports arbitrary nesting of types', function() {
Expand Down Expand Up @@ -1835,6 +1921,19 @@ describe('def', function() {
'\n' +
'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n'));

throws(function() { concat([[1, 2, 3], [Right(42), Left('XXX')]]); },
errorEq(TypeError,
'Type-variable constraint violation\n' +
'\n' +
'concat :: Array a -> Array a -> Array a\n' +
' ^\n' +
' 1\n' +
'\n' +
'1) [1, 2, 3] :: Array Number\n' +
' [Right(42), Left("XXX")] :: Array (Either String Number)\n' +
'\n' +
'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n'));

// concatNested :: [[a]] -> [[a]] -> [[a]]
var concatNested =
def('concatNested',
Expand Down

0 comments on commit 0b4db18

Please sign in to comment.