immutable-tuple
Advanced tools
Comparing version 0.4.0 to 0.4.1
@@ -6,7 +6,7 @@ 'use strict'; | ||
// A map data structure that holds object keys weakly, yet can also hold | ||
// non-object keys, unlike the native WeakMap. | ||
// non-object keys, unlike the native `WeakMap`. | ||
var UniversalWeakMap = function UniversalWeakMap() { | ||
// Since a WeakMap cannot hold primitive values as keys, we need a | ||
// backup Map instance to hold primitive keys. Both this._weakMap and | ||
// this._strongMap are lazily initialized. | ||
// Since a `WeakMap` cannot hold primitive values as keys, we need a | ||
// backup `Map` instance to hold primitive keys. Both `this._weakMap` | ||
// and `this._strongMap` are lazily initialized. | ||
this._weakMap = null; | ||
@@ -16,3 +16,3 @@ this._strongMap = null; | ||
// Since get and set are the only methods used, that's all I've | ||
// Since `get` and `set` are the only methods used, that's all I've | ||
// implemented here. | ||
@@ -29,4 +29,5 @@ | ||
this._getMap(key, true).set(key, value); | ||
// An actual Map or WeakMap would return this here, but returning the | ||
// value is more convenient below. | ||
// An actual `Map` or `WeakMap` would return `this` here, but | ||
// returning the `value` is more convenient for the `tuple` | ||
// implementation. | ||
return value; | ||
@@ -58,4 +59,10 @@ }; | ||
// Although `Symbol` is widely supported these days, we can safely fall | ||
// back to using a non-enumerable string property without violating any | ||
// assumptions elsewhere in the implementation. | ||
var useSymbol = typeof Symbol === "function"; | ||
// Used to mark `tuple.prototype` so that all objects that inherit from | ||
// any `tuple.prototype` object (there could be more than one) will test | ||
// positive according to `tuple.isTuple`. | ||
var brand = useSymbol | ||
@@ -65,2 +72,4 @@ ? Symbol.for("immutable-tuple") | ||
// Used to save a reference to the globally shared `UniversalWeakMap` that | ||
// stores all known `tuple` objects. | ||
var globalKey = useSymbol | ||
@@ -70,5 +79,6 @@ ? Symbol.for("immutable-tuple-root") | ||
// The mustConvertThisToArray value is true if the corresponding Array | ||
// method will not attempt to modify `this`, which means we can pass a | ||
// Tuple object as `this` without first converting it to an Array. | ||
// The `mustConvertThisToArray` value is true when the corresponding | ||
// `Array` method does not attempt to modify `this`, which means we can | ||
// pass a `tuple` object as `this` without first converting it to an | ||
// `Array`. | ||
function forEachArrayMethod(fn) { | ||
@@ -79,34 +89,39 @@ function call(name, mustConvertThisToArray) { | ||
} | ||
call("slice"); | ||
call("every"); | ||
call("filter"); | ||
call("find"); | ||
call("findIndex"); | ||
call("forEach"); | ||
call("includes"); | ||
call("indexOf"); | ||
call("forEach"); | ||
call("filter"); | ||
call("join"); | ||
call("lastIndexOf"); | ||
call("map"); | ||
call("every"); | ||
call("some"); | ||
call("reduce"); | ||
call("reduceRight"); | ||
call("slice"); | ||
call("some"); | ||
call("toLocaleString"); | ||
call("toString"); | ||
call("toLocaleString"); | ||
call("join"); | ||
// The `reverse` and `sort` methods are usually destructive, but for | ||
// `tuple` objects they return a new `tuple` object that has been | ||
// appropriately reversed/sorted. | ||
call("reverse", true); | ||
call("sort", true); | ||
call("lastIndexOf"); | ||
call("find"); | ||
call("findIndex"); | ||
// Make `[...someTuple]` work. | ||
call(useSymbol && Symbol.iterator || "@@iterator"); | ||
} | ||
// If this package is installed multiple times, there could be mutiple | ||
// implementations of the tuple function with distinct tuple.prototype | ||
// objects, but the shared pool of tuple objects must be the same across | ||
// all implementations. While it would be ideal to use the global object, | ||
// there's no reliable way to get the global object across all JS | ||
// environments without using the Function constructor, so instead we use | ||
// the global Array constructor as a shared namespace. | ||
var root = globalKey in Array | ||
? Array[globalKey] | ||
: def(Array, globalKey, new UniversalWeakMap, false); | ||
// See [`universal-weak-map.js`](universal-weak-map.html). | ||
// See [`util.js`](util.html). | ||
// When called with any number of arguments, this function returns an | ||
// object that inherits from `tuple.prototype` and is guaranteed to be | ||
// `===` any other `tuple` object that has exactly the same items. In | ||
// computer science jargon, `tuple` instances are "internalized" or just | ||
// "interned," which allows for constant-time equality checking, and makes | ||
// it possible for tuple objects to be used as `Map` or `WeakMap` keys, or | ||
// stored in a `Set`. | ||
function tuple() { | ||
@@ -119,5 +134,21 @@ var items = [], len = arguments.length; | ||
// If this package is installed multiple times, there could be mutiple | ||
// implementations of the `tuple` function with distinct `tuple.prototype` | ||
// objects, but the shared pool of `tuple` objects must be the same across | ||
// all implementations. While it would be ideal to use the `global` | ||
// object, there's no reliable way to get the global object across all JS | ||
// environments without using the `Function` constructor, so instead we | ||
// use the global `Array` constructor as a shared namespace. | ||
var root = globalKey in Array | ||
? Array[globalKey] | ||
: def(Array, globalKey, new UniversalWeakMap, false); | ||
function intern(array) { | ||
var node = root; | ||
// Because we are building a tree of *weak* maps, the tree will not | ||
// prevent objects in tuples from being garbage collected, since the | ||
// tree itself will be pruned over time when the corresponding `tuple` | ||
// objects become unreachable. In addition to internalization, this | ||
// property is a key advantage of the `immutable-tuple` package. | ||
array.forEach(function (item) { | ||
@@ -127,2 +158,4 @@ node = node.get(item) || node.set(item, new UniversalWeakMap); | ||
// If a `tuple` object has already been created for exactly these items, | ||
// return that object again. | ||
if (node.tuple) { | ||
@@ -134,8 +167,13 @@ return node.tuple; | ||
// Define immutable items with numeric indexes, and permanently fix the | ||
// `.length` property. | ||
array.forEach(function (item, i) { return def(t, i, item, true); }); | ||
def(t, "length", array.length, false); | ||
// Remember this new `tuple` object so that we can return the same object | ||
// earlier next time. | ||
return node.tuple = t; | ||
} | ||
// Convenient helper for defining hidden immutable properties. | ||
function def(obj, name, value, enumerable) { | ||
@@ -151,2 +189,7 @@ Object.defineProperty(obj, name, { | ||
// Since the `immutable-tuple` package could be installed multiple times | ||
// in an application, there is no guarantee that the `tuple` constructor | ||
// or `tuple.prototype` will be unique, so `value instanceof tuple` is | ||
// unreliable. Instead, to test if a value is a tuple, you should use | ||
// `tuple.isTuple(value)`. | ||
function isTuple(that) { | ||
@@ -160,16 +203,2 @@ return !! (that && that[brand] === true); | ||
// Like Array.prototype.concat, except that extra effort is required to | ||
// convert any tuple arguments to arrays, so that Array.prototype.concat | ||
// will do the right thing. | ||
var ref = Array.prototype; | ||
var concat = ref.concat; | ||
tuple.prototype.concat = function () { | ||
var args = [], len = arguments.length; | ||
while ( len-- ) args[ len ] = arguments[ len ]; | ||
return intern(concat.apply(toArray(this), args.map( | ||
function (item) { return isTuple(item) ? toArray(item) : item; } | ||
))); | ||
}; | ||
function toArray(tuple) { | ||
@@ -182,2 +211,5 @@ var array = []; | ||
// Copy all generic non-destructive Array methods to `tuple.prototype`. | ||
// This works because (for example) `Array.prototype.slice` can be invoked | ||
// against any `Array`-like object. | ||
forEachArrayMethod(function (name, desc, mustConvertThisToArray) { | ||
@@ -194,2 +226,4 @@ var method = desc && desc.value; | ||
); | ||
// Of course, `tuple.prototype.slice` should return a `tuple` object, | ||
// not a new `Array`. | ||
return Array.isArray(result) ? intern(result) : result; | ||
@@ -201,3 +235,19 @@ }; | ||
// Like `Array.prototype.concat`, except for the extra effort required to | ||
// convert any tuple arguments to arrays, so that | ||
// ``` | ||
// tuple(1).concat(tuple(2), 3) === tuple(1, 2, 3) | ||
// ``` | ||
var ref = Array.prototype; | ||
var concat = ref.concat; | ||
tuple.prototype.concat = function () { | ||
var args = [], len = arguments.length; | ||
while ( len-- ) args[ len ] = arguments[ len ]; | ||
return intern(concat.apply(toArray(this), args.map( | ||
function (item) { return isTuple(item) ? toArray(item) : item; } | ||
))); | ||
}; | ||
exports.default = tuple; | ||
exports.tuple = tuple; |
@@ -1,1 +0,1 @@ | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t=function(){this._weakMap=null,this._strongMap=null};function e(t){switch(typeof t){case"object":if(null===t)return!1;case"function":return!0;default:return!1}}t.prototype.get=function(t){var e=this._getMap(t,!1);if(e)return e.get(t)},t.prototype.set=function(t,e){return this._getMap(t,!0).set(t,e),e},t.prototype._getMap=function(t,r){return r?e(t)?this._weakMap||(this._weakMap=new WeakMap):this._strongMap||(this._strongMap=new Map):e(t)?this._weakMap:this._strongMap};var r="function"==typeof Symbol,n=r?Symbol.for("immutable-tuple"):"@@__IMMUTABLE_TUPLE__@@",o=r?Symbol.for("immutable-tuple-root"):"@@__IMMUTABLE_TUPLE_ROOT__@@";function a(t){function e(e,r){var n=Object.getOwnPropertyDescriptor(Array.prototype,e);t(e,n,!!r)}e("slice"),e("includes"),e("indexOf"),e("forEach"),e("filter"),e("map"),e("every"),e("some"),e("reduce"),e("reduceRight"),e("toString"),e("toLocaleString"),e("join"),e("reverse",!0),e("sort",!0),e("lastIndexOf"),e("find"),e("findIndex"),e(r&&Symbol.iterator||"@@iterator")}var i=o in Array?Array[o]:p(Array,o,new t,!1);function tuple(){for(var t=[],e=arguments.length;e--;)t[e]=arguments[e];return u(t)}function u(e){var r=i;if(e.forEach(function(e){r=r.get(e)||r.set(e,new t)}),r.tuple)return r.tuple;var n=Object.create(tuple.prototype);return e.forEach(function(t,e){return p(n,e,t,!0)}),p(n,"length",e.length,!1),r.tuple=n}function p(t,e,r,n){return Object.defineProperty(t,e,{value:r,enumerable:!!n,writable:!1,configurable:!1}),r}function f(t){return!(!t||!0!==t[n])}p(tuple.prototype,n,!0,!1),tuple.isTuple=f;var c=Array.prototype,l=c.concat;function s(tuple){for(var t=[],e=tuple.length;e--;)t[e]=tuple[e];return t}tuple.prototype.concat=function(){for(var t=[],e=arguments.length;e--;)t[e]=arguments[e];return u(l.apply(s(this),t.map(function(t){return f(t)?s(t):t})))},a(function(t,e,r){var n=e&&e.value;"function"==typeof n&&(e.value=function(){for(var t=[],e=arguments.length;e--;)t[e]=arguments[e];var o=n.apply(r?s(this):this,t);return Array.isArray(o)?u(o):o},Object.defineProperty(tuple.prototype,t,e))}),exports.default=tuple,exports.tuple=tuple; | ||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var t=function(){this._weakMap=null,this._strongMap=null};function e(t){switch(typeof t){case"object":if(null===t)return!1;case"function":return!0;default:return!1}}t.prototype.get=function(t){var e=this._getMap(t,!1);if(e)return e.get(t)},t.prototype.set=function(t,e){return this._getMap(t,!0).set(t,e),e},t.prototype._getMap=function(t,r){return r?e(t)?this._weakMap||(this._weakMap=new WeakMap):this._strongMap||(this._strongMap=new Map):e(t)?this._weakMap:this._strongMap};var r="function"==typeof Symbol,n=r?Symbol.for("immutable-tuple"):"@@__IMMUTABLE_TUPLE__@@",o=r?Symbol.for("immutable-tuple-root"):"@@__IMMUTABLE_TUPLE_ROOT__@@";function a(t){function e(e,r){var n=Object.getOwnPropertyDescriptor(Array.prototype,e);t(e,n,!!r)}e("every"),e("filter"),e("find"),e("findIndex"),e("forEach"),e("includes"),e("indexOf"),e("join"),e("lastIndexOf"),e("map"),e("reduce"),e("reduceRight"),e("slice"),e("some"),e("toLocaleString"),e("toString"),e("reverse",!0),e("sort",!0),e(r&&Symbol.iterator||"@@iterator")}function tuple(){for(var t=[],e=arguments.length;e--;)t[e]=arguments[e];return u(t)}var i=o in Array?Array[o]:p(Array,o,new t,!1);function u(e){var r=i;if(e.forEach(function(e){r=r.get(e)||r.set(e,new t)}),r.tuple)return r.tuple;var n=Object.create(tuple.prototype);return e.forEach(function(t,e){return p(n,e,t,!0)}),p(n,"length",e.length,!1),r.tuple=n}function p(t,e,r,n){return Object.defineProperty(t,e,{value:r,enumerable:!!n,writable:!1,configurable:!1}),r}function f(t){return!(!t||!0!==t[n])}function c(tuple){for(var t=[],e=tuple.length;e--;)t[e]=tuple[e];return t}p(tuple.prototype,n,!0,!1),tuple.isTuple=f,a(function(t,e,r){var n=e&&e.value;"function"==typeof n&&(e.value=function(){for(var t=[],e=arguments.length;e--;)t[e]=arguments[e];var o=n.apply(r?c(this):this,t);return Array.isArray(o)?u(o):o},Object.defineProperty(tuple.prototype,t,e))});var l=Array.prototype,s=l.concat;tuple.prototype.concat=function(){for(var t=[],e=arguments.length;e--;)t[e]=arguments[e];return u(s.apply(c(this),t.map(function(t){return f(t)?c(t):t})))},exports.default=tuple,exports.tuple=tuple; |
{ | ||
"name": "immutable-tuple", | ||
"version": "0.4.0", | ||
"version": "0.4.1", | ||
"description": "Immutable finite list objects with constant-time deep equality testing (===) and no memory leaks", | ||
@@ -23,2 +23,3 @@ "license": "MIT", | ||
"test": "mocha --require reify --full-trace --reporter spec test/tests.js", | ||
"docs": "docco src/*.js", | ||
"prepublish": "./scripts/build.sh" | ||
@@ -42,2 +43,3 @@ }, | ||
"devDependencies": { | ||
"docco": "^0.8.0", | ||
"mocha": "^5.0.0", | ||
@@ -44,0 +46,0 @@ "reify": "^0.13.7", |
@@ -0,2 +1,5 @@ | ||
// See [`universal-weak-map.js`](universal-weak-map.html). | ||
import { UniversalWeakMap } from "./universal-weak-map.js"; | ||
// See [`util.js`](util.html). | ||
import { | ||
@@ -8,13 +11,9 @@ brand, | ||
// If this package is installed multiple times, there could be mutiple | ||
// implementations of the tuple function with distinct tuple.prototype | ||
// objects, but the shared pool of tuple objects must be the same across | ||
// all implementations. While it would be ideal to use the global object, | ||
// there's no reliable way to get the global object across all JS | ||
// environments without using the Function constructor, so instead we use | ||
// the global Array constructor as a shared namespace. | ||
const root = globalKey in Array | ||
? Array[globalKey] | ||
: def(Array, globalKey, new UniversalWeakMap, false); | ||
// When called with any number of arguments, this function returns an | ||
// object that inherits from `tuple.prototype` and is guaranteed to be | ||
// `===` any other `tuple` object that has exactly the same items. In | ||
// computer science jargon, `tuple` instances are "internalized" or just | ||
// "interned," which allows for constant-time equality checking, and makes | ||
// it possible for tuple objects to be used as `Map` or `WeakMap` keys, or | ||
// stored in a `Set`. | ||
export default function tuple(...items) { | ||
@@ -24,8 +23,24 @@ return intern(items); | ||
// Make named imports work as well as default imports. | ||
// Named imports work as well as `default` imports. | ||
export { tuple }; | ||
// If this package is installed multiple times, there could be mutiple | ||
// implementations of the `tuple` function with distinct `tuple.prototype` | ||
// objects, but the shared pool of `tuple` objects must be the same across | ||
// all implementations. While it would be ideal to use the `global` | ||
// object, there's no reliable way to get the global object across all JS | ||
// environments without using the `Function` constructor, so instead we | ||
// use the global `Array` constructor as a shared namespace. | ||
const root = globalKey in Array | ||
? Array[globalKey] | ||
: def(Array, globalKey, new UniversalWeakMap, false); | ||
function intern(array) { | ||
let node = root; | ||
// Because we are building a tree of *weak* maps, the tree will not | ||
// prevent objects in tuples from being garbage collected, since the | ||
// tree itself will be pruned over time when the corresponding `tuple` | ||
// objects become unreachable. In addition to internalization, this | ||
// property is a key advantage of the `immutable-tuple` package. | ||
array.forEach(item => { | ||
@@ -35,2 +50,4 @@ node = node.get(item) || node.set(item, new UniversalWeakMap); | ||
// If a `tuple` object has already been created for exactly these items, | ||
// return that object again. | ||
if (node.tuple) { | ||
@@ -42,8 +59,13 @@ return node.tuple; | ||
// Define immutable items with numeric indexes, and permanently fix the | ||
// `.length` property. | ||
array.forEach((item, i) => def(t, i, item, true)); | ||
def(t, "length", array.length, false); | ||
// Remember this new `tuple` object so that we can return the same object | ||
// earlier next time. | ||
return node.tuple = t; | ||
} | ||
// Convenient helper for defining hidden immutable properties. | ||
function def(obj, name, value, enumerable) { | ||
@@ -59,2 +81,7 @@ Object.defineProperty(obj, name, { | ||
// Since the `immutable-tuple` package could be installed multiple times | ||
// in an application, there is no guarantee that the `tuple` constructor | ||
// or `tuple.prototype` will be unique, so `value instanceof tuple` is | ||
// unreliable. Instead, to test if a value is a tuple, you should use | ||
// `tuple.isTuple(value)`. | ||
function isTuple(that) { | ||
@@ -68,12 +95,2 @@ return !! (that && that[brand] === true); | ||
// Like Array.prototype.concat, except that extra effort is required to | ||
// convert any tuple arguments to arrays, so that Array.prototype.concat | ||
// will do the right thing. | ||
const { concat } = Array.prototype; | ||
tuple.prototype.concat = function (...args) { | ||
return intern(concat.apply(toArray(this), args.map( | ||
item => isTuple(item) ? toArray(item) : item | ||
))); | ||
}; | ||
function toArray(tuple) { | ||
@@ -86,2 +103,5 @@ const array = []; | ||
// Copy all generic non-destructive Array methods to `tuple.prototype`. | ||
// This works because (for example) `Array.prototype.slice` can be invoked | ||
// against any `Array`-like object. | ||
forEachArrayMethod((name, desc, mustConvertThisToArray) => { | ||
@@ -95,2 +115,4 @@ const method = desc && desc.value; | ||
); | ||
// Of course, `tuple.prototype.slice` should return a `tuple` object, | ||
// not a new `Array`. | ||
return Array.isArray(result) ? intern(result) : result; | ||
@@ -101,1 +123,13 @@ }; | ||
}); | ||
// Like `Array.prototype.concat`, except for the extra effort required to | ||
// convert any tuple arguments to arrays, so that | ||
// ``` | ||
// tuple(1).concat(tuple(2), 3) === tuple(1, 2, 3) | ||
// ``` | ||
const { concat } = Array.prototype; | ||
tuple.prototype.concat = function (...args) { | ||
return intern(concat.apply(toArray(this), args.map( | ||
item => isTuple(item) ? toArray(item) : item | ||
))); | ||
}; |
// A map data structure that holds object keys weakly, yet can also hold | ||
// non-object keys, unlike the native WeakMap. | ||
// non-object keys, unlike the native `WeakMap`. | ||
export class UniversalWeakMap { | ||
constructor() { | ||
// Since a WeakMap cannot hold primitive values as keys, we need a | ||
// backup Map instance to hold primitive keys. Both this._weakMap and | ||
// this._strongMap are lazily initialized. | ||
// Since a `WeakMap` cannot hold primitive values as keys, we need a | ||
// backup `Map` instance to hold primitive keys. Both `this._weakMap` | ||
// and `this._strongMap` are lazily initialized. | ||
this._weakMap = null; | ||
@@ -12,3 +12,3 @@ this._strongMap = null; | ||
// Since get and set are the only methods used, that's all I've | ||
// Since `get` and `set` are the only methods used, that's all I've | ||
// implemented here. | ||
@@ -25,4 +25,5 @@ | ||
this._getMap(key, true).set(key, value); | ||
// An actual Map or WeakMap would return this here, but returning the | ||
// value is more convenient below. | ||
// An actual `Map` or `WeakMap` would return `this` here, but | ||
// returning the `value` is more convenient for the `tuple` | ||
// implementation. | ||
return value; | ||
@@ -29,0 +30,0 @@ } |
@@ -0,3 +1,9 @@ | ||
// Although `Symbol` is widely supported these days, we can safely fall | ||
// back to using a non-enumerable string property without violating any | ||
// assumptions elsewhere in the implementation. | ||
const useSymbol = typeof Symbol === "function"; | ||
// Used to mark `tuple.prototype` so that all objects that inherit from | ||
// any `tuple.prototype` object (there could be more than one) will test | ||
// positive according to `tuple.isTuple`. | ||
export const brand = useSymbol | ||
@@ -7,2 +13,4 @@ ? Symbol.for("immutable-tuple") | ||
// Used to save a reference to the globally shared `UniversalWeakMap` that | ||
// stores all known `tuple` objects. | ||
export const globalKey = useSymbol | ||
@@ -12,5 +20,6 @@ ? Symbol.for("immutable-tuple-root") | ||
// The mustConvertThisToArray value is true if the corresponding Array | ||
// method will not attempt to modify `this`, which means we can pass a | ||
// Tuple object as `this` without first converting it to an Array. | ||
// The `mustConvertThisToArray` value is true when the corresponding | ||
// `Array` method does not attempt to modify `this`, which means we can | ||
// pass a `tuple` object as `this` without first converting it to an | ||
// `Array`. | ||
export function forEachArrayMethod(fn) { | ||
@@ -21,21 +30,28 @@ function call(name, mustConvertThisToArray) { | ||
} | ||
call("slice"); | ||
call("every"); | ||
call("filter"); | ||
call("find"); | ||
call("findIndex"); | ||
call("forEach"); | ||
call("includes"); | ||
call("indexOf"); | ||
call("forEach"); | ||
call("filter"); | ||
call("join"); | ||
call("lastIndexOf"); | ||
call("map"); | ||
call("every"); | ||
call("some"); | ||
call("reduce"); | ||
call("reduceRight"); | ||
call("slice"); | ||
call("some"); | ||
call("toLocaleString"); | ||
call("toString"); | ||
call("toLocaleString"); | ||
call("join"); | ||
// The `reverse` and `sort` methods are usually destructive, but for | ||
// `tuple` objects they return a new `tuple` object that has been | ||
// appropriately reversed/sorted. | ||
call("reverse", true); | ||
call("sort", true); | ||
call("lastIndexOf"); | ||
call("find"); | ||
call("findIndex"); | ||
// Make `[...someTuple]` work. | ||
call(useSymbol && Symbol.iterator || "@@iterator"); | ||
} |
Sorry, the diff of this file is not supported yet
27888
621
6