Skip to content

Commit

Permalink
no more index argument (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexreardon authored Dec 17, 2018
1 parent 7e947fd commit 06bb861
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 108 deletions.
2 changes: 1 addition & 1 deletion .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

# Provides a way to suppress flow errors in the following line.
suppress_comment= \\(.\\|\n\\)*\\$FlowFixMe
suppress_comment= \\(.\\|\n\\)*\\$ExpectEror
suppress_comment= \\(.\\|\n\\)*\\$ExpectError
185 changes: 98 additions & 87 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@ A memoization library that only caches the result of the most recent arguments.

## Rationale

Cache invalidation is hard:

> There are only two hard things in Computer Science: cache invalidation and naming things.
>
> *Phil Karlton*
So keep things simple and just use a cache size of one!

Unlike other memoization libraries, `memoize-one` only remembers the latest arguments and result. No need to worry about cache busting mechanisms such as `maxAge`, `maxSize`, `exclusions` and so on which can be prone to memory leaks. `memoize-one` simply remembers the last arguments, and if the function is next called with the same arguments then it returns the previous result.

## Usage
Expand Down Expand Up @@ -48,9 +40,47 @@ memoizedAdd(1, 2); // 3
// it is not the latest so the cached result is lost
```

### Custom equality function
## Installation

```bash
# yarn
yarn add memoize-one

# npm
npm install memoize-one --save
```

## Module usage

You can also pass in a custom function for checking the equality of two items. The equality function will be used to compare the value of every individual argument.
### ES6 module

```js
import memoizeOne from 'memoize-one';
```

### CommonJS

If you are in a CommonJS environment (eg [Node](https://nodejs.org)), then **you will need to add `.default` to your import**:

```js
const memoizeOne = require('memoize-one').default;
```

## Custom equality function

The default equality function is a simple shallow equal check

```js
const simpleIsEqual: EqualityFn = (a: mixed, b: mixed): boolean => a === b;
```

You can also pass in a custom function for checking the equality of two items.

```js
type EqualityFn = (newValue: mixed, oldValue: mixed) => boolean;
```

Here is an example that uses a deep equal check

```js
import memoizeOne from 'memoize-one';
Expand All @@ -72,105 +102,86 @@ const result4 = customMemoization({foo: 'bar'});
result3 === result4 // true - arguments are deep equal
```

#### Equality function type signature
### Custom equality function behaviour

Here is the expected [flow](http://flowtype.org) type signature for a custom equality function:
- The equality function is only called if the `this` context of the function has not changed, and the `length` of the arguments has not changed.
- The equality function is used to compare the value of every individual argument by index.

```js
type EqualityFn = (newValue: mixed, oldValue: mixed, index: number) => boolean;
```

The default equality function is a simple shallow equal check
First call: `memoized(0, 1)`
arguments: `0, 1`

```js
const simpleIsEqual: EqualityFn = (newValue: mixed, oldValue: mixed): boolean => newValue === oldValue;
```
Second call: `memoized(0, 2)`
arguments: `0, 2`

#### Equality function with multiple arguments
equality function calls:
first call: `isEqual(0, 0)`
second call: `isEqual(1, 2)`

If the function you want to memoize takes multiple arguments, your custom equality function will be called once for each argument and will be passed each argument's new value and last value.
### Custom equality function higher order functions

```js
import memoizeOne from 'memoize-one';
> ⚠️ Generally you will not need to do this. There are some rare use cases where additional information in your custom equality function can be useful. We have optimised the custom equality function api for common use cases. There is some additional work you will need to do to unlock more advanced behaviours.
const makeCountObj = (first, second, third) => ({
first: first.count,
second: second.count,
third: third.count,
});

const areCountPropertiesEqual = (newValue, oldValue) => newValue.count === oldValue.count;
// runs once for first's new and last values, once for second's, etc.

const memoizedMakeCountObj = memoizeOne(makeCountObj, areCountPropertiesEqual);

const result1 = memoizedMakeCountObj(
{a: '?', count: 1},
{a: '$', count: 2},
{a: '#', count: 3}
);
const result2 = memoizedMakeCountObj(
{b: null, count: 1},
{b: null, count: 2},
{b: null, count: 3}
);

result1 === result2; // true - same reference
```
We do not provide extra details to custom equality functions such as argument `index` for [compatibility reasons](https://github.com/alexreardon/memoize-one/issues/47). However, you can add extra information yourself to your custom equality functions with a higher order function (wrapping a function in another function).

#### Equality function index
#### Example: `index`

For each call of the equality function you are provided with the index of the argument.
Here is an example of a higher order function that allow you to pass an `index` to your custom equality function.

```js
import memoizeOne from 'memoize-one';
import deepEqual from 'lodash.isEqual';

const myEqualFn = (newValue, oldValue, index) => {
// use deep equal for first arg
if(index === 0) {
return deepEqual(newValue, oldValue);
// this function will do some special checking for the second argument
const customIsEqual = (newValue: mixed, oldValue: mixed, index: number): boolean => {
if (index === 1) {
if (!isDate(newValue) || !isDate(oldValue)) {
return false;
}
return newValue.getTime() === oldValue.getTime();
}
// use shallow equal for all other arguments
return newValue === oldValue;
}

const fn = (...args) => {
console.log('called with', ...args);
return newValue === oldValue;
};
const memoized = memoizeOne(fn, myEqualFn);

memoized({hello: 'world'}, 5);
// console.log('called with', {hello: 'world'}, 5);
const getMemoizedWithIndex = (fn: Function) => {
let argIndex: number = 0;
const withIndex = (newValue: mixed, oldValue: mixed) => customIsEqual(newValue, oldValue, argIndex++);
const memoized = memoizeOne(fn, withIndex);

memoized({hello: 'world'}, 5);
// no call to console.log
```

## Installation

```bash
# yarn
yarn add memoize-one
// every time this function is called it will reset our argIndex
return (...args: mixed[]) => {
argIndex = 0;
return memoized(...args);
};
};

# npm
npm install memoize-one --save
const memoized = getMemoizedWithIndex(myFunc);
```

## Module usage
#### Example: all `arguments`

### ES6 module
Here is an example of a higher order function that allow you to pass a `index` and `arguments` to your custom equality function.

```js
import memoizeOne from 'memoize-one';
```

### CommonJS

If you are in a CommonJS environment (eg [Node](https://nodejs.org)), then **you will need to add `.default` to your import**:
// using this to only memoize calls with 3+ arguments
const customIsEqual = (newValue: mixed, oldValue: mixed, index: number, args: mixed[]): boolean => {
if (args.length < 3) {
return false;
}
return newValue === oldValue;
};
const getMemoizedFn = (fn: Function) => {
let args: mixed[] = [];
let argIndex: number = 0;
const withIndex = (newValue: mixed, oldValue: mixed) => customIsEqual(newValue, oldValue, argIndex++, args);
const memoized = memoizeOne(fn, withIndex);

// every time this function is called it will reset our args and argIndex
return (...newArgs: mixed[]) => {
args = newArgs;
argIndex = 0;
return memoized(...newArgs);
};
};

```js
const memoizeOne = require('memoize-one').default;
const memoized = getMemoizedFn(myFunc);
```

## `this`
Expand Down Expand Up @@ -275,7 +286,7 @@ console.log(value1 === value3);
- [simple arguments](https://www.measurethat.net/Benchmarks/ShowResult/4452)
- [complex arguments](https://www.measurethat.net/Benchmarks/ShowResult/4488)

The comparisions are not exhaustive and are primiarly to show that `memoize-one` accomplishes remembering the latest invocation really fast. The benchmarks do not take into account the differences in feature sets, library sizes, parse time, and so on.
The comparisons are not exhaustive and are primarily to show that `memoize-one` accomplishes remembering the latest invocation really fast. The benchmarks do not take into account the differences in feature sets, library sizes, parse time, and so on.

## Code health :thumbsup:

Expand Down
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @flow
type EqualityFn = (newValue: mixed, oldValue: mixed, index: number) => boolean;
type EqualityFn = (newValue: mixed, oldValue: mixed) => boolean;

const simpleIsEqual: EqualityFn = (newValue: mixed, oldValue: mixed): boolean => newValue === oldValue;

Expand All @@ -15,7 +15,7 @@ export default function <ResultFn: (...Array<any>) => mixed>(resultFn: ResultFn,
let lastResult: mixed;
let calledOnce: boolean = false;

const isNewArgEqualToLast = (newArg: mixed, index: number): boolean => isEqual(newArg, lastArgs[index], index);
const isNewArgEqualToLast = (newArg: mixed, index: number): boolean => isEqual(newArg, lastArgs[index]);

// breaking cache when context (this) or arguments change
const result = function (...newArgs: Array<mixed>) {
Expand Down
Loading

0 comments on commit 06bb861

Please sign in to comment.