rubico
Version:
[a]synchronous functional programming
252 lines (247 loc) • 7.33 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[symbolIterator] == 'function') {
return FilteringIterator(value[symbolIterator](), predicate)
}
if (typeof value[symbolAsyncIterator] == 'function') {
return FilteringAsyncIterator(value[symbolAsyncIterator](), predicate)
}
if (typeof value.filter == 'function') {
return value.filter(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 Predicate = (
* value any,
* indexOrKey number|string|any,
* filterable Filterable,
* )=>(booleanResult Promise|boolean|any)
*
* filter(filterable Promise|Filterable, predicate Predicate) -> filteredFilterable Promise|Filterable
* filter(predicate Predicate)(filterable Filterable) -> filteredFilterable Promise|Filterable
* ```
*
* @description
* Filters out items from a filterable, returning a filtered filterable of the same type. The order of the items of the filterable is preserved.
*
* ```javascript [playground]
* const isOdd = number => number % 2 == 1
*
* const array = [1, 2, 3, 4, 5]
*
* const filteredArray = filter(array, isOdd)
*
* console.log(filteredArray)
* ```
*
* 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 the predicate function, which determines whether a given item from the filterable should be included in the filtered filterable.
*
* ```javascript
* const predicate = function (item) {
* // booleanResult is the boolean result of the predicate
* return booleanResult
* }
* ```
*
* The predicate function signature changes depending on the provided filterable.
*
* If the filterable is an array:
* ```coffeescript [specscript]
* predicate(item any, index number, filterable Array) -> booleanResult Promise|boolean|any
* ```
*
* If the filterable is a set:
* ```coffeescript [specscript]
* predicate(item any, item any, filterable Set) -> booleanResult Promise|boolean|any
* ```
*
* If the filterable is a map:
* ```coffeescript [specscript]
* predicate(item any, key any, filterable Map) -> booleanResult Promise|boolean|any
* ```
*
* If the filterable is a generator:
* ```coffeescript [specscript]
* predicate(item any) -> booleanResult Promise|boolean|any
* ```
*
* If the filterable is an async generator:
* ```coffeescript [specscript]
* predicate(item any) -> booleanResult Promise|boolean|any
* ```
*
* If the filterable is a plain object:
* ```coffeescript [specscript]
* predicate(item any, key string, filterable Object) -> booleanResult 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 generator, the predicate function must be synchronous.
*
* If the predicate function is asynchronous, it is executed concurrently.
*
* ```javascript [playground]
* const asyncIsOdd = async number => number % 2 == 1
*
* const array = [1, 2, 3, 4, 5]
*
* const result = await filter(array, asyncIsOdd)
*
* console.log(result)
* ```
*
* `filter` applies the predicate function to just the values of objects and maps.
*
* ```javascript [playground]
* const isOdd = number => number % 2 == 1
*
* const object = { a: 1, b: 2, c: 3, d: 4, e: 5 }
* const map = new Map([['a', 1], ['b', 2], ['c', 3], ['d', 4], ['e', 5]])
*
* const filteredObject = filter(object, isOdd)
* const filteredMap = filter(map, isOdd)
*
* console.log(filteredObject)
* console.log(filteredMap)
* ```
*
* For generators, `filter` returns a filtered generator.
*
* ```javascript [playground]
* const isOdd = number => number % 2 == 1
*
* const generateNumbers = function* () {
* yield 1; yield 2; yield 3; yield 4; yield 5
* }
*
* const numbers = generateNumbers()
* const oddNumbers = filter(generateNumbers(), isOdd)
*
* console.log('numbers')
* for (const number of numbers) {
* console.log(number)
* }
*
* console.log('odd numbers')
* for (const number of oddNumbers) {
* console.log(number)
* }
* ```
*
* If the filterable is a promise, it is resolved for its value before further execution for the eager interface only.
*
* ```javascript [playground]
* const isOdd = number => number % 2 == 1
*
* filter(Promise.resolve([1, 2, 3, 4, 5]), isOdd).then(console.log)
* ```
*
* 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