diff --git a/@vates/predicates/.USAGE.md b/@vates/predicates/.USAGE.md index be38f3ceb..f0cee9ef0 100644 --- a/@vates/predicates/.USAGE.md +++ b/@vates/predicates/.USAGE.md @@ -1,7 +1,7 @@ `undefined` predicates are ignored and `undefined` is returned if all predicates are `undefined`, this permits the most efficient composition: ```js -const compositePredicate = every(undefined, some(predicate2, undefined)) +const compositePredicate = not(every(undefined, some(not(predicate2), undefined))) // ends up as @@ -36,6 +36,21 @@ isBetween3And10(10) // → false ``` +### `not(predicate)` + +> Returns a predicate that returns the negation of the predicate. + +```js +const isEven = n => n % 2 === 0 +const isOdd = not(isEven) + +isOdd(1) +// true + +isOdd(2) +// false +``` + ### `some(predicates)` > Returns a predicate that returns `true` iff some predicate returns `true`. diff --git a/@vates/predicates/README.md b/@vates/predicates/README.md index b83161a3b..09824a9a0 100644 --- a/@vates/predicates/README.md +++ b/@vates/predicates/README.md @@ -19,7 +19,7 @@ Installation of the [npm package](https://npmjs.org/package/@vates/predicates): `undefined` predicates are ignored and `undefined` is returned if all predicates are `undefined`, this permits the most efficient composition: ```js -const compositePredicate = every(undefined, some(predicate2, undefined)) +const compositePredicate = not(every(undefined, some(not(predicate2), undefined))) // ends up as @@ -54,6 +54,21 @@ isBetween3And10(10) // → false ``` +### `not(predicate)` + +> Returns a predicate that returns the negation of the predicate. + +```js +const isEven = n => n % 2 === 0 +const isOdd = not(isEven) + +isOdd(1) +// true + +isOdd(2) +// false +``` + ### `some(predicates)` > Returns a predicate that returns `true` iff some predicate returns `true`. diff --git a/@vates/predicates/index.js b/@vates/predicates/index.js index d6132eb3d..12d3bc4f5 100644 --- a/@vates/predicates/index.js +++ b/@vates/predicates/index.js @@ -51,6 +51,22 @@ exports.every = function every() { } } +const notPredicateTag = {} +exports.not = function not(predicate) { + if (isDefinedPredicate(predicate)) { + if (predicate.tag === notPredicateTag) { + return predicate.predicate + } + + function notPredicate() { + return !predicate.apply(this, arguments) + } + notPredicate.predicate = predicate + notPredicate.tag = notPredicateTag + return notPredicate + } +} + exports.some = function some() { const predicates = handleArgs.apply(this, arguments) const n = predicates.length diff --git a/@vates/predicates/index.spec.js b/@vates/predicates/index.spec.js index 02d354e60..a8e9a58d7 100644 --- a/@vates/predicates/index.spec.js +++ b/@vates/predicates/index.spec.js @@ -3,20 +3,14 @@ const assert = require('assert/strict') const { describe, it } = require('tap').mocha -const { every, some } = require('./') +const { every, not, some } = require('./') const T = () => true const F = () => false -const testArgsHandling = fn => { - it('returns undefined if all predicates are undefined', () => { +const testArgHandling = fn => { + it('returns undefined if predicate is undefined', () => { assert.equal(fn(undefined), undefined) - assert.equal(fn([undefined]), undefined) - }) - - it('returns the predicate if only a single one is passed', () => { - assert.equal(fn(undefined, T), T) - assert.equal(fn([undefined, T]), T) }) it('throws if it receives a non-predicate', () => { @@ -24,6 +18,15 @@ const testArgsHandling = fn => { error.value = 3 assert.throws(() => fn(3), error) }) +} + +const testArgsHandling = fn => { + testArgHandling(fn) + + it('returns the predicate if only a single one is passed', () => { + assert.equal(fn(undefined, T), T) + assert.equal(fn([undefined, T]), T) + }) it('forwards this and arguments to predicates', () => { const thisArg = 'qux' @@ -36,17 +39,21 @@ const testArgsHandling = fn => { }) } -const runTests = (fn, truthTable) => +const runTests = (fn, acceptMultiple, truthTable) => it('works', () => { truthTable.forEach(([result, ...predicates]) => { + if (acceptMultiple) { + assert.equal(fn(predicates)(), result) + } else { + assert.equal(predicates.length, 1) + } assert.equal(fn(...predicates)(), result) - assert.equal(fn(predicates)(), result) }) }) describe('every', () => { testArgsHandling(every) - runTests(every, [ + runTests(every, true, [ [true, T, T], [false, T, F], [false, F, T], @@ -54,9 +61,22 @@ describe('every', () => { ]) }) +describe('not', () => { + testArgHandling(not) + + it('returns the original predicate if negated twice', () => { + assert.equal(not(not(T)), T) + }) + + runTests(not, false, [ + [true, F], + [false, T], + ]) +}) + describe('some', () => { testArgsHandling(some) - runTests(some, [ + runTests(some, true, [ [true, T, T], [true, T, F], [true, F, T], diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index d79caa667..d2aa74027 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -33,6 +33,7 @@ - @vates/nbd-client major - @vates/otp major +- @vates/predicates minor - @vates/read-chunk patch - @xen-orchestra/fs minor - @xen-orchestra/log minor