rubico
Version:
[a]synchronous functional programming
290 lines (285 loc) • 8.9 kB
JavaScript
const __ = require('./_internal/placeholder')
const curry2 = require('./_internal/curry2')
const FilteringIterator = require('./_internal/FilteringIterator')
const FilteringAsyncIterator = require('./_internal/FilteringAsyncIterator')
const isArray = require('./_internal/isArray')
const isPromise = require('./_internal/isPromise')
const arrayFilter = require('./_internal/arrayFilter')
const stringFilter = require('./_internal/stringFilter')
const setFilter = require('./_internal/setFilter')
const mapFilter = require('./_internal/mapFilter')
const objectFilter = require('./_internal/objectFilter')
const symbolIterator = require('./_internal/symbolIterator')
const symbolAsyncIterator = require('./_internal/symbolAsyncIterator')
/**
* @name _filter
*
* @synopsis
* ```coffeescript [specscript]
* _filter(
* array Array,
* arrayPredicate (value any, index number, array Array)=>Promise|boolean
* ) -> filteredArray Promise|Array
*
* _filter(
* object Object,
* objectPredicate (value any, key string, object Object)=>Promise|boolean
* ) -> filteredObject Promise|Object
*
* _filter(
* set Set,
* setPredicate (value any, value, set Set)=>Promise|boolean
* ) -> filteredSet Promise|Set
*
* _filter(
* map Map,
* mapPredicate (value any, key any, map Map)=>Promise|boolean
* ) -> filteredMap Promise|Map
*
* _filter(
* generatorFunction GeneratorFunction,
* predicate (value any)=>Promise|boolean
* ) -> filteringGeneratorFunction GeneratorFunction
*
* _filter(
* asyncGeneratorFunction AsyncGeneratorFunction,
* predicate (value any)=>Promise|boolean
* ) -> filteringAsyncGeneratorFunction AsyncGeneratorFunction
*
* _filter(
* reducer Reducer,
* predicate (value any)=>Promise|boolean
* ) -> filteringReducer Reducer
* ```
*/
const _filter = function (value, predicate) {
if (isArray(value)) {
return arrayFilter(value, predicate)
}
if (value == null) {
return value
}
if (typeof value == 'string' || value.constructor == String) {
return stringFilter(value, predicate)
}
if (value.constructor == Set) {
return setFilter(value, predicate)
}
if (value.constructor == Map) {
return mapFilter(value, predicate)
}
if (typeof value.filter == 'function') {
return value.filter(predicate)
}
if (typeof value[symbolIterator] == 'function') {
return FilteringIterator(value[symbolIterator](), predicate)
}
if (typeof value[symbolAsyncIterator] == 'function') {
return FilteringAsyncIterator(value[symbolAsyncIterator](), predicate)
}
if (value.constructor == Object) {
return objectFilter(value, predicate)
}
return value
}
/**
* @name filter
*
* @synopsis
* ```coffeescript [specscript]
* type Filterable = Array|Set|Map|Generator|AsyncGenerator|{ filter: function }|Object
*
* type SyncOrAsyncPredicate = (
* value any,
* indexOrKey number|string|any,
* filterable Filterable,
* )=>(condition Promise|boolean)
*
* filter(filterable Promise|Filterable, predicate SyncOrAsyncPredicate) -> result Promise|Filterable
* filter(predicate SyncOrAsyncPredicate)(filterable Filterable) -> result Promise|Filterable
* ```
*
* @description
* Filters out elements from a filterable. Returns a filterable of the same type. The order of the elements in the filterable is preserved.
*
* The following data types are considered to be filterables:
* * `array`
* * `set`
* * `map`
* * `generator`
* * `async generator`
* * `object with .filter method`
* * `object`
*
* The filtering operation is defined by a given predicate function. The predicate function dictates whether a given element from the filterable should be included in the returned filterable.
*
* ```javascript
* const predicate = function (element) {
* // condition is the boolean result of the predicate test on element
* return condition
* }
* ```
*
* The predicate function signature changes depending on the provided filterable.
*
* If the filterable is an array:
* ```coffeescript [specscript]
* predicate(element any, index number, filt Array) -> condition Promise|boolean|any
* ```
*
* If the filterable is a set:
* ```coffeescript [specscript]
* predicate(element any, element any, filt Set) -> condition Promise|boolean|any
* ```
*
* If the filterable is a map:
* ```coffeescript [specscript]
* predicate(element any, key any, filt Map) -> condition Promise|boolean|any
* ```
*
* If the filterable is a generator:
* ```coffeescript [specscript]
* predicate(element any) -> condition Promise|boolean|any
* ```
*
* If the filterable is an async generator:
* ```coffeescript [specscript]
* predicate(element any) -> condition Promise|boolean|any
* ```
*
* If the filterable is an object with a `.filter` method, the predicate function signature is defined externally.
*
* If the filterable is a plain object:
* ```coffeescript [specscript]
* predicate(element any, key string, filt Object) -> condition Promise|boolean|any
* ```
*
* `filter` works for arrays.
*
* ```javascript [playground]
* const isOdd = number => number % 2 == 1
*
* const array = [1, 2, 3, 4, 5]
*
* const result = filter(array, isOdd)
* console.log(result) // [1, 3, 5]
* ```
*
* If the predicate is asynchronous, the returned promise is concurrently resolved for its boolean condition before continuing with the filtering operation.
*
* ```javascript [playground]
* const asyncIsOdd = async number => number % 2 == 1
*
* const array = [1, 2, 3, 4, 5]
*
* const promise = filter(array, asyncIsOdd)
* promise.then(console.log) // [1, 3, 5]
* ```
*
* `filter` applies the predicate function to just the values of an object.
*
* ```javascript [playground]
* const isOdd = number => number % 2 == 1
*
* const obj = { a: 1, b: 2, c: 3, d: 4, e: 5 }
*
* const result = filter(obj, isOdd)
* console.log(result) // { a: 1, c: 3, e: 5 }
* ```
*
* `filter` applies the predicate to the values of the entries of a map.
*
* ```javascript [playground]
* const isOdd = number => number % 2 == 1
*
* const myMap = new Map([['a', 1], ['b', 2], ['c', 3], ['d', 4], ['e', 5]])
*
* const result = filter(myMap, isOdd)
* console.log(result) // Map(3) { 'a' => 1, 'c' => 3, 'e' => 5 }
* ```
*
* For generators, `filter` returns a lazily filtered generator. All values that are normally yielded by the generator that test false by the predicate are excluded from the returned generator.
*
* ```javascript [playground]
* const isOdd = number => number % 2 == 1
*
* const numbersGeneratorFunction = function* () {
* yield 1; yield 2; yield 3; yield 4; yield 5
* }
*
* const numbersGenerator = numbersGeneratorFunction()
* const oddNumbersGenerator = filter(numbersGeneratorFunction(), isOdd)
*
* for (const number of numbersGenerator) {
* console.log(number) // 1
* // 2
* // 3
* // 4
* // 5
* }
*
* for (const number of oddNumbersGenerator) {
* console.log(number) // 1
* // 3
* // 5
* }
* ```
*
* For async generators, `filter` returns a lazily filtered async generator. All values that are normally yielded by the async generator that test falsy by the predicate are excluded from the returned async generator.
*
* ```javascript [playground]
* const asyncIsOdd = async number => number % 2 == 1
*
* const asyncNumbersGeneratorFunction = async function* () {
* yield 1; yield 2; yield 3; yield 4; yield 5
* }
*
* const asyncNumbersGenerator = asyncNumbersGeneratorFunction()
*
* const asyncOddNumbersGenerator = filter(asyncNumbersGeneratorFunction(), asyncIsOdd)
*
* for await (const number of asyncNumbersGenerator) {
* console.log(number) // 1
* // 2
* // 3
* // 4
* // 5
* }
*
* for await (const number of asyncOddNumbersGenerator) {
* console.log(number) // 1
* // 3
* // 5
* }
* ```
*
* Any promises passed in argument position are resolved for their values before further execution. This only applies to the eager version of the API.
*
* ```javascript [playground]
* const isOdd = number => number % 2 == 1
*
* filter(Promise.resolve([1, 2, 3, 4, 5]), isOdd).then(console.log)
* // [1, 3, 5]
* ```
*
* See also:
* * [forEach](/docs/forEach)
* * [map](/docs/map)
* * [reduce](/docs/reduce)
* * [transform](/docs/transform)
* * [flatMap](/docs/flatMap)
* * [some](/docs/some)
*
* @execution concurrent
*
* @transducing
*/
const filter = function (arg0, arg1) {
if (typeof arg0 == 'function') {
return curry2(_filter, __, arg0)
}
return isPromise(arg0)
? arg0.then(curry2(_filter, __, arg1))
: _filter(arg0, arg1)
}
module.exports = filter