Skip to content

Instantly share code, notes, and snippets.

@dtinth
Created August 17, 2017 17:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dtinth/e4329efd3882f926a134cb8a746d57a8 to your computer and use it in GitHub Desktop.
Save dtinth/e4329efd3882f926a134cb8a746d57a8 to your computer and use it in GitHub Desktop.
const memoizeArraySelector = (baseSelector) => {
let _last = [ ]
return (state) => {
const result = baseSelector(state)
if (arrayEquals(result, _last)) return _last
_last = result
return result
}
}
export const mapSelector = f => baseSelector => createSelector(baseSelector, f)
export const mapSelectorForArray = f => baseSelector => memoizeArraySelector(mapSelector(f)(baseSelector))
export const filteredBy = predicate => mapSelectorForArray(data => _.filter(data, predicate))
export const rejectedBy = predicate => mapSelectorForArray(data => _.reject(data, predicate))
export const sortedBy = sortBy => mapSelectorForArray(data => _.sortBy(data, sortBy))
export const sortedByOrder = (sortByOrder, direction) => mapSelectorForArray(data => _.orderBy(data, sortByOrder, direction))
@dtinth
Copy link
Author

dtinth commented Aug 17, 2017

Version with less abstraction:

export const filteredBy =
  predicate => baseSelector =>
    memoizeArraySelector(state => _.filter(baseSelector(state), predicate))

@dtinth
Copy link
Author

dtinth commented Aug 17, 2017

@iboss-ptk suggests adding type to make code more clear ^_^

@iboss-ptk
Copy link

@dtinth I tried adding flow type to it but it's not pretty at all, it looks even scarier. Haskell-style type definition is much better to convey the meaning but not available in js :(

@iboss-ptk
Copy link

iboss-ptk commented Aug 19, 2017

@dtinth not sure if this is practical working with team environment introducing Haskell-style type definition for documentation purpose. I guess it depends and feels like it quite interesting.

To think even further. Can we have this Haskell-ish type def as type checking tool like what flow does?

// "It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures."
//      —Alan Perlis
// So, would it be better if we just memoize everything, no?
// Apart from composability point of view, it also simplifies the function name.
// Or is there any performance/space concern?

// memoizeSelector :: Selector s a -> Selector s a
const memoizeSelector = (baseSelector) => {
  // memoize array only for now...
  let _last = [ ]

  return (state) => {
    const result = baseSelector(state)
    if (_.isArray(result) && arrayEquals(result, _last)) return _last
    _last = result
    return result
  }
}

// smap :: (a -> b) -> Selector s a -> Selector s b
export const smap = f => baseSelector => memoizeSelector(createSelector(baseSelector, f))


// filterBy :: (a -> Bool) -> Selector s a[] -> Selector s a[]
export const filterBy = predicate => smap(data => _.filter(data, predicate))

// rejectBy :: (a -> Bool) -> Selector s a[] -> Selector s a[]
export const rejectBy = predicate => smap(data => _.reject(data, predicate))

// sortBy :: (a -> Bool) -> Selector s a[] -> Selector s a[]
export const sortBy = sortBy => smap(data => _.sortBy(data, sortBy))

// sortByOrder :: Object a => (Keys a, Direction[]) -> Selector s a[] -> Selector s a[]
export const sortByOrder = (order, direction) => smap(data => _.orderBy(data, sortByOrder, direction))

@iboss-ptk
Copy link

iboss-ptk commented Aug 19, 2017

By the way, I really like your idea having selector as a Functor. I have never thought about it before.
(actually, to think about it, this is not just a functor but can make it a Reader monad isn't it?)

And if we are able to use pipeline operator |> it would be even cooler.

// completedTasksSortedByTitle :: Selector GlobalState Task[]
const completedTasksSortedByTitle =
  tasks
    |> filterBy(task => task.completed)
    |> sortBy(task => task.title)

@dtinth
Copy link
Author

dtinth commented Aug 19, 2017

@iboss-ptk

  • About flow type, yep I agreed that Flow/TypeScript type notation makes it look very scary.

  • About making memoizeSelector work with both array and non-array, I like it a lot.

  • Yes, it can be a Reader monad. For example, using createSelector to combine selectors can be considered a monadic operation:

    // Assume a selector is a fantasy-land monad instead of a (s => a) function.
    const combine2 = s1 => s2 => f => s1.chain(v1 => s2.map(v2 => f(v1, v2)))

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