Skip to content

Instantly share code, notes, and snippets.

@drdaeman
Last active January 8, 2017 22:50
Show Gist options
  • Save drdaeman/760ade50b6cb5a58caea110ff6e650f9 to your computer and use it in GitHub Desktop.
Save drdaeman/760ade50b6cb5a58caea110ff6e650f9 to your computer and use it in GitHub Desktop.
RethinkDB-resembling type-aware value comparison (experiment)
const _ = require("lodash");
function inferTypeOf(value) {
// Given a value from RethinkDB, tries to infer its ReQL type.
// Returns a string value, similar to r.typeOf result, or an empty
// string if the type is not recognized or unsupported.
switch (typeof value) {
case "string":
return "STRING";
case "number":
return "NUMBER";
case "boolean":
return "BOOL";
case "null":
return "NULL";
case "object":
// TODO: Implement check for PTYPE<GEOMETRY>
if (_.isArray(value)) {
return "ARRAY";
} else if (_.isDate(value)) {
return "PTYPE<TIME>";
} else if (_.isTypedArray(value)) {
return "PTYPE<BINARY>";
} else {
return "OBJECT";
}
default:
return "";
}
}
function spaceship(a, b) {
// A simple three-way comparison ("spaceship operator") function.
// Using JS built-in comparison logic, returns 0, -1 or 1,
// depending on whenever values compare equal, less or greater.
// Returns null if neither of comparisons succeed (non-comparable).
if (a === b) {
return 0;
} else if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return null;
}
}
function compareValues(a, b) {
// Compares two values in a (hopefully) same manner as RethinkDB would do.
// Returns 0, -1 or 1 (see `spaceship` function) or null if cannot compare.
/* if (typeof a === "string" && typeof b === "string") { // Uncomment this to experiment with fast-path for strings
return a === b ? 0 : (a < b ? -1 : 1);
} */
const ta = inferTypeOf(a),
tb = inferTypeOf(b);
if (ta === "" || tb === "") {
return null;
}
if (ta !== tb) {
return spaceship(ta, tb);
}
switch (ta) {
case "STRING":
case "NUMBER":
case "BOOL":
case "NULL":
return spaceship(a, b);
case "PTYPE<TIME>":
return spaceship(a.getTime(), b.getTime());
case "PTYPE<BINARY>":
return null; // unsupported
case "ARRAY":
for (let pair of _.zip(a, b)) {
if (pair[0] === undefined) {
return -1;
} else if (pair[1] === undefined) {
return 1;
} else {
const c = compareValues(pair[0], pair[1]);
if (c !== 0) {
return c;
}
}
}
return 0;
case "OBJECT":
return compareValues(_.toPairs(a).sort(), _.toPairs(b).sort());
default:
return null;
}
}
module.exports = compareValues;
const timeit = require("timeit");
const uuidV4 = require('uuid/v4');
const compareValues = require("./index.js");
function usingCompareValues(done) {
const a = uuidV4(), b = uuidV4();
var res = compareValues(a, b);
done();
return res;
}
function usingJSComparison(done) {
const a = uuidV4(), b = uuidV4();
var res = a === b ? 0 : (a < b ? -1 : (a > b ? 1 : null));
done();
return res;
}
timeit.howlong(300000, [usingCompareValues, usingJSComparison], function (err, results) {
console.log('Baseline', results[0]);
console.log('Using compareValues', results[1]);
console.log('Using JS comparison', results[2]);
});
@internalfx
Copy link

Took a quick look, and I have to say this looks much better!

Great work. It is very easy to follow the logic in my head with this code.

I haven't tested this yet, but assuming everything works like it says on the tin, I may be able to merge this eventually.

@internalfx
Copy link

Would you mind Submitting a new separate PR with this?

If so I will merge it into a separate testing branch in the main project.

@drdaeman
Copy link
Author

drdaeman commented Jan 8, 2017

@internalfx Sorry, haven't noticed your comments here earlier. I've created a PR, internalfx/thinker#11

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment