Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Functional style #2908

Open
wants to merge 79 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
c22e1e9
Simplify _.iteratee
jgonggrijp May 14, 2020
ce15d38
Make _.contains generic over the collection type
jgonggrijp May 15, 2020
f0e9bc3
Use _.find in _.each
jgonggrijp May 15, 2020
0b51eae
Use _.find in _.every and _.some
jgonggrijp May 15, 2020
f4e0f71
Use _.each in createReduce
jgonggrijp May 15, 2020
11bdeab
Use _.chain in _chainResult
jgonggrijp Dec 13, 2020
3bc4c50
Slim down use of _.each in _.filter
jgonggrijp Dec 13, 2020
ea55aeb
Use _.each in _.map
jgonggrijp Dec 14, 2020
5092fb5
Import modules/iteratee.js for its side effect in modules/_cb.js
jgonggrijp Dec 20, 2020
008990f
Use isArrayLike in _.isEmpty
jgonggrijp Dec 30, 2020
bd14ef1
Remove an unnecessary check in _.omit
jgonggrijp Dec 21, 2020
38129be
Remove an unnecessary check from _.pick
jgonggrijp Dec 30, 2020
be5b8a2
Deduplicate subexpressions in _.sample
jgonggrijp Dec 30, 2020
adfb2f8
Reorder and document _.toArray
jgonggrijp Dec 22, 2020
e842fd5
Simplify and deduplicate _.uniq
jgonggrijp Dec 22, 2020
5d73fb3
Use getLength in collectNonEnumProps
jgonggrijp Dec 30, 2020
3753fe7
Use getLength in _.bindAll
jgonggrijp Dec 30, 2020
d7393b4
Refactor _.first and _.last, using getLength
jgonggrijp Dec 21, 2020
18fd848
Use getLength in _.invoke
jgonggrijp Dec 24, 2020
296be9e
Remove switch pyramid of doom from _.restArguments
jgonggrijp Dec 21, 2020
60319af
Simplify _.restArguments using array.slice
jgonggrijp Dec 21, 2020
645eafe
Use _.last in _.invoke
jgonggrijp Dec 24, 2020
aac1042
Use _.initial in _.invoke
jgonggrijp Dec 24, 2020
431b84e
Simplify internal flatten using array.push
jgonggrijp Dec 30, 2020
5781d89
Use array.push in _.partial
jgonggrijp Dec 30, 2020
81a7a77
Factor out linearSearch and binarySearch base algorithms
jgonggrijp Dec 30, 2020
b4ab797
Use linearSearch in _.has
jgonggrijp Dec 30, 2020
5bc6d47
Use linearSearch in _.intersection
jgonggrijp Dec 30, 2020
eef39cd
Use linearSearch in the internal flatten
jgonggrijp May 16, 2020
b63e425
Use linearSearch in _methodFingerprint
jgonggrijp Dec 13, 2020
2fac273
Polish use of linearSearch in _.intersection
jgonggrijp Dec 13, 2020
9624b1b
Use linearSearch in _.mapObject
jgonggrijp Dec 14, 2020
3120fd2
Use linearSearch in collectNonEnumProps
jgonggrijp Dec 30, 2020
3a326ad
Use linearSearch in internal flatten, again
jgonggrijp Dec 30, 2020
1f3562a
Use linearSearch in _.bindAll
jgonggrijp Dec 30, 2020
cba898c
Use linearSearch in _.chunk
jgonggrijp Dec 21, 2020
230fee1
Use linearSearch in _.isEqual
jgonggrijp Dec 21, 2020
a0ef35a
Replace _.each by linearSearch in _.mixin and the array methods
jgonggrijp Dec 21, 2020
8764f04
Use linearSearch in _.pick
jgonggrijp Dec 30, 2020
bc0e942
Use linearSearch in _.sample
jgonggrijp Dec 30, 2020
65c16db
Inline _.sortedIndex and _.sortedLastIndex
jgonggrijp Dec 21, 2020
8cfa13f
Replace linearSearch by _.times in _.sample
jgonggrijp Dec 21, 2020
8edde7b
Use _.times in _.unzip
jgonggrijp Dec 22, 2020
19ba06e
Use linearSearch in collectNonEnumProps, again
jgonggrijp Dec 24, 2020
5f46a25
Swap the final parameters of linearSearch
jgonggrijp Dec 26, 2020
dfd6d6b
Rename the final linearSearch parameter
jgonggrijp Dec 26, 2020
13fca95
Use _.lastIndexOf in _.isEqual
jgonggrijp Dec 26, 2020
457a96d
Use negated isUndefined in _.find
jgonggrijp Dec 21, 2020
7daecbe
Use restArguments in _createAssigner
jgonggrijp Dec 13, 2020
ce3f0ed
Use _.restArguments in _.intersection
jgonggrijp Dec 14, 2020
c654128
Use _.restArguments in _.partial
jgonggrijp Dec 30, 2020
fca3114
Enable customization of accumulator initialization in createReduce
jgonggrijp Dec 19, 2020
8743fc0
Refactor _.min and _.max (fix #2688)
jgonggrijp Dec 19, 2020
bd49157
Use _.find instead of _.reduce in extremum
jgonggrijp Dec 27, 2020
5f72f90
Revert "Enable customization of accumulator initialization in createR…
jgonggrijp Dec 27, 2020
c71244a
Restore the identity optimization in extremum
jgonggrijp Dec 27, 2020
e108783
Put back the for loop in extremum
jgonggrijp Dec 27, 2020
e2a9a83
Inline compareNumeric in _.min and _.max
jgonggrijp Dec 27, 2020
448d2e8
Use boolean state variable, not shape-shifting callbacks in extremum
jgonggrijp Dec 27, 2020
5165f1f
Don't use _.partial anymore in _.min and _.max
jgonggrijp Dec 27, 2020
9bf9dc3
Enable find-by-strict-equality function elision in linearSearch
jgonggrijp Dec 28, 2020
64e62ef
Use _.find instead of _.each in createReducer
jgonggrijp Dec 29, 2020
aaa0fcb
Eliminate the dir variable in createReduce
jgonggrijp Dec 29, 2020
01f324e
Reuse the start variable in createIndexFinder
jgonggrijp Dec 29, 2020
6a0ebd8
Use _.find instead of _.each in _.map
jgonggrijp Dec 31, 2020
7fee0d5
Use _.find instead of _.each in internal group
jgonggrijp Dec 31, 2020
f60a3ef
Use _.find instead of _.each in _.filter
jgonggrijp Dec 31, 2020
ba7df7a
Use _.times instead of linearSearch in _.chunk
jgonggrijp Jan 2, 2021
1cb9003
Use _.findKey instead of linearSearch in _.mapObject
jgonggrijp Jan 2, 2021
674ed39
Elide createPredicateIndexFinder
jgonggrijp Jan 2, 2021
b7637b6
Slim down predicate elision in linearSearch
jgonggrijp Jan 2, 2021
d5b8d74
Move the identity optimization back to _.min and _.max
jgonggrijp Jan 2, 2021
b99a079
Inline decideNumeric in _.min and _.max
jgonggrijp Jan 2, 2021
9de1ceb
Use forward iteration in _.bindAll
jgonggrijp Jan 4, 2021
20e88e5
Use forward iteration in _.isEqual
jgonggrijp Jan 4, 2021
d82eadc
Move the decide parameter out of the way in extremum
jgonggrijp Jan 4, 2021
640332b
Generalize _.sample over the collection types
jgonggrijp Jan 4, 2021
341ad30
Restore only the essence of former optimizeCb
jgonggrijp Jan 21, 2021
eaba5b5
Replace bindCb by bindCb4 where appropriate
jgonggrijp Jan 21, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 0 additions & 17 deletions modules/_baseIteratee.js

This file was deleted.

13 changes: 13 additions & 0 deletions modules/_binarySearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import getLength from './_getLength.js';

// Iteratively cut `array` in half to figure out the index at which `obj` should
// be inserted so as to maintain the order defined by `compare`.
export default function binarySearch(array, obj, iteratee, compare) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a more general version of _.sortedIndex, using compare instead of builtin operator <. _.sortedIndex now calls binarySearch internally.

var value = iteratee(obj);
var low = 0, high = getLength(array);
while (low < high) {
var mid = Math.floor((low + high) / 2);
if (compare(iteratee(array[mid]), value)) low = mid + 1; else high = mid;
}
return low;
}
8 changes: 8 additions & 0 deletions modules/_bindCb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Internal function that returns a bound version of the
// passed-in callback, used in `_.iteratee`.
export default function bindCb(func, context) {
if (context === void 0) return func;
return function() {
return func.apply(context, arguments);
};
}
11 changes: 11 additions & 0 deletions modules/_bindCb4.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// In Firefox, `Function.prototype.call` is faster than
// `Function.prototype.apply`. In the optimized variant of
// `bindCb` below, we exploit the fact that no Underscore
// function passes more than four arguments to a callback.
// **NOT general enough for use outside of Underscore.**
export default function bindCb4(func, context) {
if (context === void 0) return func;
return function(a1, a2, a3, a4) {
return func.call(context, a1, a2, a3, a4);
};
}
16 changes: 9 additions & 7 deletions modules/_cb.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import isFunction from './isFunction.js';
import bindCb4 from './_bindCb4.js';
import _ from './underscore.js';
import baseIteratee from './_baseIteratee.js';
import iteratee from './iteratee.js';
import './iteratee.js';

// The function we call internally to generate a callback. It invokes
// `_.iteratee` if overridden, otherwise `baseIteratee`.
export default function cb(value, context, argCount) {
if (_.iteratee !== iteratee) return _.iteratee(value, context);
return baseIteratee(value, context, argCount);
// The function we call internally to generate a callback: a wrapper
// of `_.iteratee`, which uses `bindCb4` instead of `bindCb` for
// function iteratees. It also saves some bytes in the minified code.
export default function cb(value, context) {
if (isFunction(value)) return bindCb4(value, context);
return _.iteratee(value, context);
}
4 changes: 2 additions & 2 deletions modules/_chainResult.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import _ from './underscore.js';
import chain from './chain.js';

// Helper function to continue chaining intermediate results.
export default function chainResult(instance, obj) {
return instance._chain ? _(obj).chain() : obj;
return instance._chain ? chain(obj) : obj;
}
9 changes: 4 additions & 5 deletions modules/_collectNonEnumProps.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import linearSearch from './_linearSearch.js';
import { nonEnumerableProps, ObjProto } from './_setup.js';
import isFunction from './isFunction.js';
import has from './_has.js';
Expand All @@ -8,7 +9,7 @@ import has from './_has.js';
// arrays of strings.
function emulatedSet(keys) {
var hash = {};
for (var l = keys.length, i = 0; i < l; ++i) hash[keys[i]] = true;
linearSearch(keys, function(key) { hash[key] = true; });
return {
contains: function(key) { return hash[key]; },
push: function(key) {
Expand All @@ -23,18 +24,16 @@ function emulatedSet(keys) {
// needed.
export default function collectNonEnumProps(obj, keys) {
keys = emulatedSet(keys);
var nonEnumIdx = nonEnumerableProps.length;
var constructor = obj.constructor;
var proto = isFunction(constructor) && constructor.prototype || ObjProto;

// Constructor is a special case.
var prop = 'constructor';
if (has(obj, prop) && !keys.contains(prop)) keys.push(prop);

while (nonEnumIdx--) {
prop = nonEnumerableProps[nonEnumIdx];
linearSearch(nonEnumerableProps, function(prop) {
if (prop in obj && obj[prop] !== proto[prop] && !keys.contains(prop)) {
keys.push(prop);
}
}
}, -1); /* legacy backwards iteration */
}
14 changes: 8 additions & 6 deletions modules/_createAssigner.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import restArguments from './restArguments.js';

// An internal function for creating assigner functions.
export default function createAssigner(keysFunc, defaults) {
return function(obj) {
var length = arguments.length;
return restArguments(function(obj, sources) {
var length = sources.length;
if (defaults) obj = Object(obj);
if (length < 2 || obj == null) return obj;
for (var index = 1; index < length; index++) {
var source = arguments[index],
if (!length || obj == null) return obj;
for (var index = 0; index < length; index++) {
var source = sources[index],
keys = keysFunc(source),
l = keys.length;
for (var i = 0; i < l; i++) {
Expand All @@ -14,5 +16,5 @@ export default function createAssigner(keysFunc, defaults) {
}
}
return obj;
};
});
}
44 changes: 22 additions & 22 deletions modules/_createIndexFinder.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import getLength from './_getLength.js';
import { slice } from './_setup.js';
import binarySearch from './_binarySearch.js';
import identity from './identity';
import less from './_less.js';
import lessEqual from './_lessEqual.js';
import isNaN from './isNaN.js';
import linearSearch from './_linearSearch.js';

// Internal function to generate the `_.indexOf` and `_.lastIndexOf` functions.
export default function createIndexFinder(dir, predicateFind, sortedIndex) {
return function(array, item, idx) {
var i = 0, length = getLength(array);
if (typeof idx == 'number') {
if (dir > 0) {
i = idx >= 0 ? idx : Math.max(idx + length, i);
} else {
length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
}
} else if (sortedIndex && idx && length) {
idx = sortedIndex(array, item);
return array[idx] === item ? idx : -1;
// Internal function to generate the `indexOf` and `lastIndexOf` functions.
export default function createIndexFinder(dir) {
var forward = dir > 0;
var compare = forward ? less : lessEqual;
// `controlArg` may be either a number indicating the first index to start
// searching at, or a boolean indicating whether the array is sorted by native
// operator `<`.
return function(array, item, controlArg) {
var start;
if (getLength(array) < 1) return -1;
if (typeof controlArg == 'number') {
start = controlArg;
} else if (controlArg && forward) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The && forward condition is only here to prevent the isSorted option from creeping into _.lastIndexOf as a side effect, purely because I vowed not to make any interface changes on this branch. It can be removed in a future update.

start = binarySearch(array, item, identity, compare);
return array[start] === item ? start : -1;
}
if (item !== item) {
idx = predicateFind(slice.call(array, i, length), isNaN);
return idx >= 0 ? idx + i : -1;
}
for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
if (array[idx] === item) return idx;
}
return -1;
var predicate = item !== item ? isNaN : { value: item };
return linearSearch(array, predicate, dir, start);
};
}
15 changes: 0 additions & 15 deletions modules/_createPredicateIndexFinder.js

This file was deleted.

34 changes: 17 additions & 17 deletions modules/_createReduce.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import isArrayLike from './_isArrayLike.js';
import keys from './keys.js';
import optimizeCb from './_optimizeCb.js';
import bindCb4 from './_bindCb4.js';

// Internal helper to create a reducing function, iterating left or right.
export default function createReduce(dir) {
// Create a reducing function iterating in the same way as `loop` (e.g.
// `_.find`).
export default function createReduce(loop) {
// Wrap code that reassigns argument variables in a separate function than
// the one that accesses `arguments.length` to avoid a perf hit. (#1991)
var reducer = function(obj, iteratee, memo, initial) {
var _keys = !isArrayLike(obj) && keys(obj),
length = (_keys || obj).length,
index = dir > 0 ? 0 : length - 1;
function reducer(obj, iteratee, memo, initial) {
if (!initial) {
memo = obj[_keys ? _keys[index] : index];
index += dir;
}
for (; index >= 0 && index < length; index += dir) {
var currentKey = _keys ? _keys[index] : index;
memo = iteratee(memo, obj[currentKey], currentKey, obj);
// Make the `iteratee` change identity temporarily so that it only sets
// the `memo` on the first iteration.
var actualIteratee = iteratee;
iteratee = function(memo, value) {
iteratee = actualIteratee;
return value;
}
}
loop(obj, function(value, key, obj) {
memo = iteratee(memo, value, key, obj);
});
return memo;
};
}

return function(obj, iteratee, memo, context) {
var initial = arguments.length >= 3;
return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
return reducer(obj, bindCb4(iteratee, context), memo, initial);
};
}
33 changes: 33 additions & 0 deletions modules/_extremum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import identity from './identity.js';
import cb from './_cb.js';
import find from './find.js';

// The general algorithm behind `_.min` and `_.max`. `compare` should return
// `true` if its first argument is more extreme than (i.e., should be preferred
// over) its second argument, `false` otherwise. `iteratee` and `context`, like
// in other collection functions, let you map the actual values in `collection`
// to the values to `compare`.
export default function extremum(collection, compare, iteratee, context, decide) {
// `extremum` is essentially a combined map+reduce with **two** accumulators:
// `result` and `iterResult`, respectively the unmapped and the mapped version
// corresponding to the same element.
var result, iterResult;
iteratee = cb(iteratee, context);
var first = true;
find(collection, function(value, key) {
var iterValue = iteratee(value, key, collection);
if (first || compare(iterValue, iterResult)) {
result = value;
iterResult = iterValue;
first = false;
}
});
Comment on lines +14 to +24
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This loop represents the "slow path" that I mentioned in the opening post. The fast path is currently still duplicated in _.min and _.max, for reasons explained under _.max.

// `extremum` would normally return `result`. However, `_.min` and `_.max`
// forcibly return a number even if there is no element that maps to a numeric
// value. Passing both accumulators through `decide` before returning enables
// this behavior.
// `decide` is an optional customization point which is only present for the
// above historical reason; please don't use it, as it will likely be removed
// in the future.
return (decide || identity)(result, iterResult);
}
14 changes: 5 additions & 9 deletions modules/_flatten.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import getLength from './_getLength.js';
import linearSearch from './_linearSearch.js';
import isArrayLike from './_isArrayLike.js';
import isArray from './isArray.js';
import isArguments from './isArguments.js';
Expand All @@ -11,21 +11,17 @@ export default function flatten(input, depth, strict, output) {
} else if (depth <= 0) {
return output.concat(input);
}
var idx = output.length;
for (var i = 0, length = getLength(input); i < length; i++) {
var value = input[i];
linearSearch(input, function(value) {
if (isArrayLike(value) && (isArray(value) || isArguments(value))) {
// Flatten current level of array or arguments object.
if (depth > 1) {
flatten(value, depth - 1, strict, output);
idx = output.length;
} else {
var j = 0, len = value.length;
while (j < len) output[idx++] = value[j++];
linearSearch(value, function(item) { output.push(item); });
}
} else if (!strict) {
output[idx++] = value;
output.push(value);
}
}
});
return output;
}
4 changes: 4 additions & 0 deletions modules/_greater.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// A version of the `>` operator that can be passed around as a function.
export default function greater(left, right) {
return left > right;
}
4 changes: 2 additions & 2 deletions modules/_group.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import cb from './_cb.js';
import each from './each.js';
import find from './find.js';

// An internal function used for aggregate "group by" operations.
export default function group(behavior, partition) {
return function(obj, iteratee, context) {
var result = partition ? [[], []] : {};
iteratee = cb(iteratee, context);
each(obj, function(value, index) {
find(obj, function(value, index) {
var key = iteratee(value, index, obj);
behavior(result, value, key);
});
Expand Down
4 changes: 4 additions & 0 deletions modules/_less.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// A version of the `<` operator that can be passed around as a function.
export default function less(left, right) {
return left < right;
}
4 changes: 4 additions & 0 deletions modules/_lessEqual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// A version of the `<=` operator that can be passed around as a function.
export default function lessEqual(left, right) {
return left <= right;
}
28 changes: 28 additions & 0 deletions modules/_linearSearch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import getLength from './_getLength.js';
import isFunction from './isFunction.js';

// Internal function for linearly iterating over arrays.
export default function linearSearch(array, predicate, dir, start) {
var target, length = getLength(array);
dir || (dir = 1);
start = (
start == null ? (dir > 0 ? 0 : length - 1) :
start < 0 ? (dir > 0 ? Math.max(0, start + length) : start + length) :
dir > 0 ? start : Math.min(start, length - 1)
);
// As a special case, in order to elide the `predicate` invocation on every
// loop iteration, we allow the caller to pass a value that should be found by
// strict equality comparison. This is somewhat like a rudimentary iteratee
// shorthand. It is used in `_.indexof` and `_.lastIndexOf`.
if (!isFunction(predicate)) {
target = predicate && predicate.value;
predicate = false;
}
for (; start >= 0 && start < length; start += dir) {
if (
predicate ? predicate(array[start], start, array) :
array[start] === target
) return start;
}
return -1;
}