Skip to content

Bunch of stateful predicate wrappers. RxJS & Array.filter compliant.

License

Notifications You must be signed in to change notification settings

tomaskraus/stateful-predicates

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

65 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

build codecov Code Style: Google

stateful-predicates

Carefully selected, minimalistic collection of predicate wrappers.
stateful-predicates bring new power to standard predicates, required by Array.filter, RxJS or other methods/libraries that use predicates.

Predicate list:

Example: Get an inside of a block documetation comment:

import {switchTrueFalse, nthElementAfter} from 'stateful-predicates';

// prettier-ignore
const linesInsideADocBlockComment = lines.filter(
    switchTrueFalse(
      nthElementAfter(1,        // start to "return true" one line after a `/**`
        s => /\/\*\*/i.test(s)
      ),  
      s => /\*\//i.test(s)      // start to "return false" on a line with `*/`
    )
  );

see a complete example

stateful-predicates library is all about predicates applied to elements of some list. That list is typically an iterable.
When a predicate is applied to an element, that predicate can either succeed on that element (i.e. match that element), or not.

Why to use

  • No state variable mess as it uses closures to preserve the state.
  • Functional programming friendly.
  • Typed. With d.ts for Javascript.
  • Well tested. 100% code coverage.

Installation

$ npm install stateful-predicates

Usage

Typescript / ES module:

import * as SP from 'stateful-predicates';

Javascript / CommonJS:

const SP = require('stateful-predicates');

API

TPredicate

type TPredicate<T> = (value: T, index: number) => boolean;

Predicate is a function, that accepts some value and returns a boolean value, based on its condition.

It's something you can pass as callback to Array.filter or RxJS operators.

Almost every function of stateful-predicates library accepts at least one TPredicate argument and returns another TPredicate value.

switchTrueFalse

function switchTrueFalse<T>(
  predicateForTrue: TPredicate<T>,
  predicateForFalse: TPredicate<T>
): TPredicate<T>;

Returns a predicate(value, index) P that fulfills the following:

  1. P stays true "on and after" predicateForTrue has succeeded on some element
  2. P becomes false again "on and after" predicateForFalse has succeeded on some element that follows.
    At the beginning, P is false.
  3. P is reusable: able to switch true/false multiple times.
  4. P is greedy:
  • switches to true on the first of consecutive elements predicateForTrue can succeed
  • switches to false on the first of consecutive elements predicateForFalse can succeed

Example:

const elementsEnclosedByZeroAndMinusOne = [2, 1, 0, 0, 5, 9, -1, -1, 7].filter(
  switchTrueFalse(
    x => x === 0,
    x => x === -1
  )
);
console.log(elementsEnclosedByZeroAndMinusOne);
//=> [ 0, 0, 5, 9 ]

nthElementAfter

function nthElementAfter<T>(
  offset: number,
  parentPredicate: TPredicate<T>
): TPredicate<T>;

Returns predicate(value, index) P, that:

  • returns true if its parentPredicate has "succeeded at element" offset number of elements before.

Example:

const isThree = (x: number) => x === 3;
const secondElemsAfter3 = [2, 3, 0, 7, 4, 3, 5, -8].filter(
  nthElementAfter(2, isThree)
);
console.log(secondElemsAfter3);
//=> [ 7, -8 ]

It kind of shifts (or delays) the succesful element evaluation.

  • P is greedy: tries to succeed as soon as possible. If there are more elements within the "offset range" parentPredicate could succeed, they are not recognized.
  • P is repeatable: is ready to detect elements again as soon as it is at least offset elements after its last detected element.
const result = [3, 2, 2, 2, 5, 1].map(nthElementAfter(1, x => x === 2));
console.log(result);
//=> [ false, false, true, false, true, false ]

nthMatch

function nthMatch<T>(n: number, parentPredicate: TPredicate<T>): TPredicate<T>;

Returns predicate(value, index) P, that:

  • returns true if its parentPredicate has succeeded n times

Example:

const isEven = x => x % 2 === 0;
const secondMatchingElem = [2, 3, 5, 4, 8, 5, -8].filter(nthMatch(2, isEven));
console.log(secondMatchingElem);
//=> [ 4 ]`

onChange

function onChange<T>(parentPredicate: TPredicate<T>): TPredicate<T>;

Returns predicate(value, index) P, that:

  • returns true whenever its parentPredicate changes value - i.e. result of parent predicate differs from P's internal state.

At the begin, the internal state of P is false.

Example:

const isThree = (x: number) => x === 3;
const changes = [2, 3, 3, 3, 4, 3, 5, -8].map(onChange(isThree));
console.log(changes);
//=> [ false, true, false, false, true, true, true, false ]

Complete Example

Show only documentation comments from a source code input text:

import {switchTrueFalse, nthElementAfter} from 'stateful-predicates';

const input = `
  /** 
   * greaterThanOne
   * @param x number value
   * @returns true if x is greater than one, false otherwise
   */
   */
  function greaterThanOne(x: number): boolean {
    return x > 1;
  }

  /**
   * An increment function
   * @param x number value
   * @returns that value incremented by one
   */
  const inc = (x: number) => ++x;`;

const docCommentPredicate = () =>
  switchTrueFalse<string>(
    s => /\/\*\*/.test(s), // true at '/**' (begin-mark)
    nthElementAfter(1, s => /\*\//.test(s)) // false after '*/' (end-mark)
  );

// prettier-ignore
const onlyDocComments = input
  .split('\n')
  .filter(docCommentPredicate())
  .join('\n');
console.log(onlyDocComments);
//=> /**
//    * greaterThanOne
//    * @param x number value
//    * @returns true if x is greater than one, false otherwise
//    */
//   /**
//    * An increment function
//    * @param x number value
//    * @returns that value incremented by one
//    */