CoNarrative / framework-x

A reasonable programming framework.
https://framework-x.io
MIT License
19 stars 2 forks source link

Port latest reselect #37

Open alex-dixon opened 4 years ago

alex-dixon commented 4 years ago

@mikegai Let me know the best place to get this from

mikegai commented 4 years ago

latest remote-dm master

mikegai commented 4 years ago
import { lastInputOutputMemoize } from './reselect-raw'

function defaultEqualityCheck(a, b) {
  return a === b
}

function areArgumentsShallowlyEqual(equalityCheck, prev, next) {
  if (prev === null || next === null || prev.length !== next.length) {
    return false
  }

  // Do this in a for loop (and not a `forEach` or an `every`) so we can determine equality as fast as possible.
  const length = prev.length
  for (let i = 0; i < length; i++) {
    if (!equalityCheck(prev[i], next[i])) {
      return false
    }
  }

  return true
}

export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
  let lastArgs = null
  let lastResult = null
  // we reference arguments instead of spreading them for performance reasons
  return function() {
    if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
      // apply arguments instead of spreading for performance.
      lastResult = func.apply(null, arguments)
    }

    lastArgs = arguments
    return lastResult
  }
}

function getDependencies(funcs) {
  const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs

  if (!dependencies.every(dep => typeof dep === 'function')) {
    const dependencyTypes = dependencies.map(dep => typeof dep).join(', ')
    throw new Error(
      'Selector creators expect all input-selectors to be functions, ' +
        `instead received the following types: [${dependencyTypes}]`
    )
  }

  return dependencies
}

export function makeSelectorCreator(memoize, outerMemoize, ...memoizeOptions) {
  return (...funcs) => {
    let recomputations = 0
    const resultFunc = funcs.pop()
    const dependencies = getDependencies(funcs)

    // If a selector is called with the exact same arguments we don't need to traverse our dependencies again.
    const memoizedResultFunc = memoize(function() {
      recomputations++
      // apply arguments instead of spreading for performance.
      return resultFunc.apply(null, arguments)
    }, ...memoizeOptions)

    const memoizeDb = outerMemoize || memoize
    // this is the selector itself -- we don't pass in anything but a DB bag to our selectors...
    const selector = memoizeDb(function() {
      const params = []
      const length = dependencies.length

      for (let i = 0; i < length; i++) {
        // apply arguments instead of spreading and mutate a local list of params for performance.
        params.push(dependencies[i].apply(null, arguments))
      }

      // apply arguments instead of spreading for performance.
      return memoizedResultFunc.apply(null, params)
    })

    selector.resultFunc = resultFunc
    selector.dependencies = dependencies
    selector.recomputations = () => recomputations
    selector.resetRecomputations = () => (recomputations = 0)
    return selector
  }
}

export const createSelector = makeSelectorCreator(defaultMemoize)

export function createStructuredSelector(
  selectors,
  selectorCreator = createSelector
) {
  if (typeof selectors !== 'object') {
    throw new Error(
      'createStructuredSelector expects first argument to be an object ' +
        `where each property is a selector, instead received a ${typeof selectors}`
    )
  }
  const objectKeys = Object.keys(selectors)
  return selectorCreator(objectKeys.map(key => selectors[key]), (...values) => {
    return values.reduce((composition, value, index) => {
      composition[objectKeys[index]] = value
      return composition
    }, {})
  })
}

/**
 * Stateful selectors with an "accumulator bag"
 */

const defaultIs = (a, b) =>
  areArgumentsShallowlyEqual(defaultEqualityCheck, a, b)
/*
 * experiment # 2 in fancy diff selectors
 * return in the form [acc, item]
 * */
export function accumulatorMemoize(func) {
  let lastArgs = null
  let lastResult = null
  let acc = {}
  return function() {
    if (!defaultIs(lastArgs, arguments)) {
      const fullArgs = [acc].concat(Array.from(arguments))
      const ret = func.apply(null, fullArgs)
      if (!ret)
        throw new Error(
          'Stateful selectors must return a stream accumulator as first argument'
        )
      acc = ret[0]
      lastResult = ret[1]
    }
    lastArgs = arguments

    return lastResult
  }
}
export const createStatefulSelector = makeSelectorCreator(
  accumulatorMemoize,
  defaultMemoize,
  { diff: true }
)
mikegai commented 4 years ago

exact same as latest reselect, except makeSelectorCreator takes two different memoization functions instead of one

alex-dixon commented 4 years ago

Removing as release blocker