Joe
Joe

Reputation: 4254

Use pipe filter functions with arguments in RamdaJS

Say I got these two filters:

const getNamesStartingWith = (letter) => persons => {
  return persons.filter(person => person.name.startsWith(letter);  
}

const getPersonsStartingWithZipCode = (zipCode) => persons => {
  return persons.filter(person => person.address.zipCode.startsWith(zipCode);
}

I want to make a generic pipe filter.

const pipeFilter = (funcArr, args) => arr => {
...
}

The funcArr is an array of the functions to run. The args is a double array where the index is the arguments of the functions.

So with my example functions it would be:

const pipeFilter = ([getNamesStartingWith, getPersonsStartingWithZipCode], [['J'], [4]]) => persons => {..}

The argument for getNamesStartingWith is J. The argument for getPersonsStartingWithZipCode is 4

If I would to it 'manually' I would to something like:

export const doPipeFiltering = (funcArr: any[], args: any[]) => (arr) => {

  return funcArr.reduce((acc, func, index) => {

    let filterdArr;
    if (index === 0) {
      filterdArr = func(...args[index])(arr);
    } else {
      filterdArr = func(...args[index])(acc);
    }
    acc = filterdArr;
    return acc;
  }, []);
};

Works. But I would like to do it in RamdaJs so I can use all neat functions there.

I don't find how can apply arguments for differet filter functions in Ramda. Is it possible?

Upvotes: 0

Views: 284

Answers (1)

Scott Sauyet
Scott Sauyet

Reputation: 50807

You can definitely make this a fair bit cleaner using Ramda functions. Here's one approach:

const doPipeFiltering = curry ( (funcArr, args, arr) => 
  reduce ( (acc, func) => func (acc), arr, zipWith (apply, funcArr, args) ))

const getNamesStartingWith = (letter) => (persons) => {
  return persons.filter(person => person.name.startsWith(letter)) 
}

const getPersonsStartingWithZipCode = (zipCode) => persons => {
  return persons.filter(person => person.address.zipCode.startsWith(zipCode))
}
                      
const people = [
  {name: 'Amanda', address: {zipCode: '86753'}}, 
  {name: 'Aaron', address: {zipCode: '09867'}},
  {name: 'Amelia', address: {zipCode: '85309'}}, 
  {name: 'Bob', address: {zipCode: '67530'}}
]

console .log (
  doPipeFiltering (
    [getNamesStartingWith, getPersonsStartingWithZipCode], 
    [['A'], ['8']],
    people
  )
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {curry, reduce, zipWith, apply} = R                    </script>

But I would suggest that this is not a very ergonomic API. First of all, if the filtering functions had an entirely generic interface, it might not be so bad, but now you have to supply two separate array parameters, whose values must be synchronized. That to me is a recipe for disaster.

Secondly, what we have to create for this is functions that filter a list. I would find it much cleaner if the code handled the filtering, and we only supplied a set of simple predicates.

So this is an alternative suggestion, for an API that I think is much cleaner:

const runFilters = useWith (filter, [allPass, identity] )

// or one of these
// const runFilter = curry((filters, xs) => filter(allPass(filters), xs))
// const runFilters = curry ((filters, xs) => reduce ((a, f) => filter (f, a), xs, filters))

const nameStartsWith = (letter) => (person) => person.name.startsWith (letter)
const zipStartsWith = (digits) => (person) => person.address.zipCode.startsWith (digits)

const myFilter = runFilters ([nameStartsWith ('A'), zipStartsWith ('8')])

const people = [
  {name: 'Amanda', address: {zipCode: '86753'}}, 
  {name: 'Aaron', address: {zipCode: '09867'}},
  {name: 'Amelia', address: {zipCode: '85309'}}, 
  {name: 'Bob', address: {zipCode: '67530'}}
]

console .log (
  myFilter (people)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script>const {useWith, filter, allPass, identity} = R               </script>

Note that here the main function is simpler, the filter predicates are simpler, and the call is much more explicit. Especially improved is the readability of nameStartsWith ('A') and zipStartsWith ('8')

Upvotes: 2

Related Questions