Such Much Code
Such Much Code

Reputation: 827

How do I refactor this code using ramda.js?

I'm using mithril.js and ramda.js. I have the following code in mithril:

return m('ul.list-inline.text-center', [
     ctrl.items.map(item => R.allPass(item.policies)(item) ? m('li', m('a', {
          href: item.uri,
          config: m.route
      }, item.name)) : null)
]);

So basically I'm iterating over ctrl.items and item.policies is just an array of simple functions returning true / false. Therefore, R.allPass(item.policies)(item) gives me either true if everything passes or false otherwise and then this decides whether to render li tag or not.

So instead of this functionality, I thought it would be better to create this more functional way as I will be able to reuse the code. So instead of map, I thought I would filter down the items using that R.allPass and then just render out the filtered array instead.

I started fiddling around with a simple filter in ramda, but I cannot figure out how to achieve the result:

var filterItems = R.filter(
    R.pipe(
        R.prop('policies'),
        R.allPass               
    )
);

This just returns the full array. It doesn't exclude any items at all.

Upvotes: 1

Views: 131

Answers (1)

Scott Sauyet
Scott Sauyet

Reputation: 50807

Well, from what I can make of your types, the issue is that you're expecting a function to do double-duty.

Assuming items contains a list of objects such as this:

const items = [
  {
    name: 'foo',
    val: 13,
    policies: [
      obj => obj.val > 10,
      obj => obj.val < 20,
      obj => obj.val % 2 == 1,
    ]
  },
  {
    name: 'bar',
    val: 14,
    policies: [
      obj => obj.val > 10,
      obj => obj.val < 20,
      obj => obj.val % 2 == 1,
    ]
  }
];

then this:

R.pipe(
    R.prop('policies'),
    R.allPass               
)

will have a type something like:

const testPolicies = R.pipe(
    R.prop('policies'), // :: Item -> [(Item -> Boolean)]
    R.allPass           // :: [(Item -> Boolean)] -> Item -> Boolean
)                    // => :: Item -> Item -> Boolean

You need to pass two copies of your item to this function to get it to return a boolean. But you treat it as though it would work with just one, as though it could serve as an input to filter :: (Item -> Boolean) -> [Item] -> [Item].

You need a way to transform your (Item -> Item -> Boolean) into a (Item -> Boolean).

There is nothing built into Ramda to help with this, although perhaps Ramda should consider adding this, as it's occasionally useful. But one of the standard combinators1, known as W would do this:

const W = f => x => f(x)(x);

You could then use it like this:

R.filter(W(testPolicies), items); //=>
// {
//   name: 'foo',
//   val: 13,
//   policies: [
//     obj => obj.val > 10,
//     obj => obj.val < 20,
//     obj => obj.val % 2 == 1,
//   ]
// }

You can see this in action on the Ramda REPL.


1 There is a nice list of combinators in Avaq's gist.

Upvotes: 1

Related Questions