Đinh Carabus
Đinh Carabus

Reputation: 3494

How to chain multiple "or" or "and" statements in ramda and preserve short-circuit evaluation?

I would like to filter an array of posts against a user defined text. Each post has an id and a text property which should be searched. If the search-text is an empty string all posts should obviously be displayed - no need to check if the other predicates resolve to true. Currently I'm doing something like this:

const hasText = R.curry((text, post) => R.reduce(R.or, false, [
    R.equals("", text),
    R.includes(text, post.text),
    R.includes(text, post.id.toString())
]))

const posts = [{id: 1, text: "a"},{id: 2, text: "b"},{id: 3, text: "c"}]

console.log(R.filter(hasText("b"), posts));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>

The problem here is that all of the predicates are evaluated upfront even though that is unneccessary.

Is it possible to achieve the same effect as using plain || in a more functional way using ramda?

Upvotes: 1

Views: 841

Answers (2)

Chad S.
Chad S.

Reputation: 6631

You can use the built-in R.propSatisfies function in a manner similar to the other answer's propIncludes function:

const textContains = curry(
  (srch, value) => pipe(toString, includes(srch))(value)
)

const postHasText = curry(
  (text, post) => anyPass([
        propSatisfies(textContains(text), 'text'),
        propSatisfies(textContains(text), 'id')
  ])(post)
)

const postsWithText = curry(
  (text, posts) => filter(postHasText(text), posts)
)

Alternatively, as shown in the example below, you can make an intermediate helper method propsContain and then postHasText is a bit simpler, and you can reuse the propsContain for other things too.

const { curry, pipe, toString, props, includes, filter, any, isEmpty } = R

const textContains = curry(
  (srch, value) => pipe(toString, includes(srch))(value)
)

const propsContain = curry(
  (srch, propList, entity) => pipe(props(propList), any(textContains(srch)))(entity)
)

const postHasText = propsContain(R.__, ['id', 'text'], R.__)

const searchPosts = curry(
  (qry, posts) => isEmpty(qry) ? posts : filter(postHasText(qry), posts)
)

const posts = [{id: "a", text: "foo"},{id: 2, text: "box"},{id: 3, text: "bat"}]
const results = searchPosts('a', posts)
console.log(results)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>

Upvotes: 2

Ori Drori
Ori Drori

Reputation: 192422

If the text is an empty string, you don't need to filter the array at all. To check if any of the predicates is truthy, you can use R.any:

const { curry, pipe, prop, toString, includes, filter, anyPass, isEmpty } = R

// check if prop includes text
const propIncludes = curry((key, text) => pipe(prop(key), toString, includes(text)))

// filter an array of posts, and check if any of the prop contains the text
const hasText = curry((text, posts) => filter(anyPass([
  propIncludes('text', text),
  propIncludes('id', text),
]))(posts))

// skips filtering if text is empty
const checkText = curry((text, posts) => isEmpty(text) ? posts : hasText(text, posts))

const posts = [{id: 1, text: "a"},{id: 2, text: "b"},{id: 3, text: "c"}]

const result = checkText('b', posts)

console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>

Upvotes: 2

Related Questions