Emulate Java 8 predicate chaining in JavaScript I am trying to emulate Java 8's predicate chaining in JavaScript.
So far it works, but is there a more efficient way to write this? Furthermore, I had to create get and run methods to return the predicate so that is can be passed into a filter function.
Update: I added an _unwrap method to unwrap the internal predicate function so that you can pass a Predicate to another Predicate.


const records = [
  { name: 'Andrew' , age: 21, position: 'Developer' },
  { name: 'Bill'   , age: 26, position: 'Developer' },
  { name: 'Chris'  , age: 45, position: 'Manager'   },
  { name: 'Dave'   , age: 30, position: 'Developer' },
  { name: 'Eric'   , age: 32, position: 'Manager'   }
];

class Predicate {
  constructor(predicateFn) {
    this.predicateFn = this._unwrap(predicateFn);
  }
  /* @private */ _unwrap(predicateFn) {
    return predicateFn instanceof Predicate ? predicateFn.get() : predicateFn;
  }
  and(predicateFn) {
    return new Predicate((item) =&gt; this.predicateFn(item) &amp;&amp; this._unwrap(predicateFn)(item));
  }
  or(predicateFn) {
    return new Predicate((item) =&gt; this.predicateFn(item) || this._unwrap(predicateFn)(item));
  }
  not() {
    return new Predicate((item) =&gt; !this.predicateFn(item));
  }
  get() {
    return this.predicateFn;
  }
  run(arr) {
    return arr.filter(this.predicateFn);
  }
}

const display = ({ name, age, position }) =&gt; `${name} (${age}) [${position}]`;

const ageOver21AndDeveloper = new Predicate(({ age }) =&gt; age &gt; 21)
  .and(({ position }) =&gt; position === 'Developer');

console.log(ageOver21AndDeveloper.run(records).map(display).join(', '));

const billOrManager = new Predicate(({ name }) =&gt; name === 'Bill')
  .or(({ position }) =&gt; position === 'Manager');

console.log(billOrManager.run(records).map(display).join(', '));

const notUnder30 = new Predicate(({ age }) =&gt; age &lt; 30).not();

console.log(notUnder30.run(records).map(display).join(', '));

// Pass predicate object to another predicate.
console.log(billOrManager.or(ageOver21AndDeveloper).run(records).map(display).join(', '));
.as-console-wrapper { top: 0; max-height: 100% !important; }
I believe you could retain the same level of flexibility without managing extra objects by using higher order functions. This also has the advantage of treating the predicates as functions and nothing more.


const records = [
  { name: 'Andrew' , age: 21, position: 'Developer' },
  { name: 'Bill'   , age: 26, position: 'Developer' },
  { name: 'Chris'  , age: 45, position: 'Manager'   },
  { name: 'Dave'   , age: 30, position: 'Developer' },
  { name: 'Eric'   , age: 32, position: 'Manager'   }
];

const display = ({ name, age, position }) =&gt;
     `${name} (${age}) [${position}]`;

const not = condition =&gt; (...args) =&gt; !condition(...args);

const and = (...conditions) =&gt; (...args) =&gt;
    conditions.reduce(
        (a,b)=&gt;a&amp;&amp;b(...args),true
    )

const or = (...conditions) =&gt; (...args) =&gt;
    conditions.reduce(
        (a,b)=&gt;a||b(...args),false
    )

const ageOver21 = ({age}) =&gt; age &gt; 21
const isDeveloper = ({position}) =&gt; position === 'Developer';
const ageOver21AndIsDeveloper = and(ageOver21,isDeveloper);

console.log(records.filter(ageOver21AndIsDeveloper).map(display).join(', '));

const billOrManager = or(
    ({name}) =&gt; name === 'Bill',
    ({position}) =&gt; position === 'Manager'
)

console.log(records.filter(billOrManager).map(display).join(', '));

const notUnder30 = not(({ age }) =&gt; age &lt; 30)

console.log(records.filter(notUnder30).map(display).join(', '));

console.log(
    records.filter(
        or(billOrManager,ageOver21AndIsDeveloper)
    )
    .map(display).join(', ')
);