UNPKG

rubico

Version:

[a]synchronous functional programming

861 lines (780 loc) 22.6 kB
/** * rubico v2.6.2 * https://github.com/a-synchronous/rubico * (c) 2019-2024 Richard Tong * rubico may be freely distributed under the MIT license. */ const isPromise = value => value != null && typeof value.then == 'function' const symbolIterator = Symbol.iterator const MappingIterator = (iterator, mapper) => ({ toString() { return '[object MappingIterator]' }, [symbolIterator]() { return this }, next() { const iteration = iterator.next() return iteration.done ? iteration : { value: mapper(iteration.value), done: false } }, }) const NextIteration = value => ({ value, done: false }) const symbolAsyncIterator = Symbol.asyncIterator const MappingAsyncIterator = (asyncIterator, mapper) => ({ [symbolAsyncIterator]() { return this }, async next() { const iteration = await asyncIterator.next() if (iteration.done) { return iteration } const mapped = mapper(iteration.value) return isPromise(mapped) ? mapped.then(NextIteration) : { value: mapped, done: false } } }) const __ = Symbol.for('placeholder') // argument resolver for curry2 const curry2ResolveArg0 = ( baseFunc, arg1, ) => function arg0Resolver(arg0) { return baseFunc(arg0, arg1) } // argument resolver for curry2 const curry2ResolveArg1 = ( baseFunc, arg0, ) => function arg1Resolver(arg1) { return baseFunc(arg0, arg1) } const curry2 = function (baseFunc, arg0, arg1) { return arg0 == __ ? curry2ResolveArg0(baseFunc, arg1) : curry2ResolveArg1(baseFunc, arg0) } // argument resolver for curry3 const curry3ResolveArg0 = ( baseFunc, arg1, arg2, ) => function arg0Resolver(arg0) { return baseFunc(arg0, arg1, arg2) } // argument resolver for curry3 const curry3ResolveArg1 = ( baseFunc, arg0, arg2, ) => function arg1Resolver(arg1) { return baseFunc(arg0, arg1, arg2) } // argument resolver for curry3 const curry3ResolveArg2 = ( baseFunc, arg0, arg1, ) => function arg2Resolver(arg2) { return baseFunc(arg0, arg1, arg2) } const curry3 = function (baseFunc, arg0, arg1, arg2) { if (arg0 == __) { return curry3ResolveArg0(baseFunc, arg1, arg2) } if (arg1 == __) { return curry3ResolveArg1(baseFunc, arg0, arg2) } return curry3ResolveArg2(baseFunc, arg0, arg1) } const isArray = Array.isArray const isObject = value => { if (value == null) { return false } const typeofValue = typeof value return (typeofValue == 'object') || (typeofValue == 'function') } const promiseAll = Promise.all.bind(Promise) const arrayMap = function (array, mapper) { const arrayLength = array.length, result = Array(arrayLength) let index = -1, isAsync = false while (++index < arrayLength) { const resultItem = mapper(array[index], index, array) if (isPromise(resultItem)) { isAsync = true } result[index] = resultItem } return isAsync ? promiseAll(result) : result } const callPropUnary = (value, property, arg0) => value[property](arg0) const stringMap = function (string, mapper) { const result = arrayMap(string, mapper) return isPromise(result) ? result.then(curry3(callPropUnary, __, 'join', '')) : result.join('') } const always = value => function getter() { return value } const setMap = function (set, mapper) { const result = new Set(), promises = [] for (const item of set) { const resultItem = mapper(item, item, set) if (isPromise(resultItem)) { promises.push(resultItem.then(curry3(callPropUnary, result, 'add', __))) } else { result.add(resultItem) } } return promises.length == 0 ? result : promiseAll(promises).then(always(result)) } // argument resolver for curry4 const curry4ResolveArg0 = ( baseFunc, arg1, arg2, arg3, ) => function arg0Resolver(arg0) { return baseFunc(arg0, arg1, arg2, arg3) } // argument resolver for curry4 const curry4ResolveArg1 = ( baseFunc, arg0, arg2, arg3, ) => function arg1Resolver(arg1) { return baseFunc(arg0, arg1, arg2, arg3) } // argument resolver for curry4 const curry4ResolveArg2 = ( baseFunc, arg0, arg1, arg3, ) => function arg2Resolver(arg2) { return baseFunc(arg0, arg1, arg2, arg3) } // argument resolver for curry4 const curry4ResolveArg3 = ( baseFunc, arg0, arg1, arg2, ) => function arg3Resolver(arg3) { return baseFunc(arg0, arg1, arg2, arg3) } const curry4 = function (baseFunc, arg0, arg1, arg2, arg3) { if (arg0 == __) { return curry4ResolveArg0(baseFunc, arg1, arg2, arg3) } if (arg1 == __) { return curry4ResolveArg1(baseFunc, arg0, arg2, arg3) } if (arg2 == __) { return curry4ResolveArg2(baseFunc, arg0, arg1, arg3) } return curry4ResolveArg3(baseFunc, arg0, arg1, arg2) } const callPropBinary = (value, property, arg0, arg1) => value[property](arg0, arg1) const mapMap = function (value, mapper) { const result = new Map(), promises = [] for (const [key, item] of value) { const resultItem = mapper(item, key, value) if (isPromise(resultItem)) { promises.push(resultItem.then( curry4(callPropBinary, result, 'set', key, __))) } else { result.set(key, resultItem) } } return promises.length == 0 ? result : promiseAll(promises).then(always(result)) } const promiseObjectAllExecutor = object => function executor(resolve) { const result = {} let numPromises = 0 for (const key in object) { const value = object[key] if (isPromise(value)) { numPromises += 1 value.then((key => function (res) { result[key] = res numPromises -= 1 if (numPromises == 0) { resolve(result) } })(key)) } else { result[key] = value } } if (numPromises == 0) { resolve(result) } } const promiseObjectAll = object => new Promise(promiseObjectAllExecutor(object)) const objectMap = function (object, mapper) { const result = {} let isAsync = false for (const key in object) { const resultItem = mapper(object[key], key, object) if (isPromise(resultItem)) { isAsync = true } result[key] = resultItem } return isAsync ? promiseObjectAll(result) : result } const funcConcat = ( funcA, funcB, ) => function pipedFunction(...args) { const intermediate = funcA(...args) return isPromise(intermediate) ? intermediate.then(funcB) : funcB(intermediate) } const objectSet = function (object, property, value) { object[property] = value return object } const arrayMapSeriesAsync = async function ( array, mapper, result, index, ) { const arrayLength = array.length while (++index < arrayLength) { const resultItem = mapper(array[index], index) result[index] = isPromise(resultItem) ? await resultItem : resultItem } return result } const arrayMapSeries = function (array, mapper) { const arrayLength = array.length, result = Array(arrayLength) let index = -1 while (++index < arrayLength) { const resultItem = mapper(array[index], index) if (isPromise(resultItem)) { return resultItem.then(funcConcat( curry3(objectSet, result, index, __), curry4(arrayMapSeriesAsync, array, mapper, __, index))) } result[index] = resultItem } return result } const stringMapSeries = function (string, mapper) { const result = arrayMapSeries(string, mapper) return isPromise(result) ? result.then(curry3(callPropUnary, __, 'join', '')) : result.join('') } const thunkify4 = (func, arg0, arg1, arg2, arg3) => function thunk() { return func(arg0, arg1, arg2, arg3) } // _objectMapSeriesAsync( // object Object, // f function, // result Object, // doneKeys Object // ) -> Promise<object> const _objectMapSeriesAsync = async function (object, f, result, doneKeys) { for (const key in object) { if (key in doneKeys) { continue } let resultItem = f(object[key]) if (isPromise(resultItem)) { resultItem = await resultItem } result[key] = resultItem } return result } const objectMapSeries = function (object, f) { const result = {} const doneKeys = {} for (const key in object) { doneKeys[key] = true const resultItem = f(object[key], key, object) if (isPromise(resultItem)) { return resultItem.then(funcConcat( curry3(objectSet, result, key, __), thunkify4(_objectMapSeriesAsync, object, f, result, doneKeys), )) } result[key] = resultItem } return result } const thunkify3 = (func, arg0, arg1, arg2) => function thunk() { return func(arg0, arg1, arg2) } const setAdd = function (set, value) { set.add(value) return set } // _setMapSeriesAsync( // iterator Iterator, // f function, // result Set, // ) -> Promise<Set> const _setMapSeriesAsync = async function (iterator, f, result) { let iteration = iterator.next() while (!iteration.done) { let resultItem = f(iteration.value) if (isPromise(resultItem)) { resultItem = await resultItem } result.add(resultItem) iteration = iterator.next() } return result } const setMapSeries = function (set, f) { const result = new Set() const iterator = set[symbolIterator]() let iteration = iterator.next() while (!iteration.done) { const resultItem = f(iteration.value) if (isPromise(resultItem)) { return resultItem.then(funcConcat( curry2(setAdd, result, __), thunkify3(_setMapSeriesAsync, iterator, f, result), )) } result.add(resultItem) iteration = iterator.next() } return result } const mapSet = function setting(source, key, value) { return source.set(key, value) } // _mapMapSeriesAsync( // iterator Iterator, // f function, // result Map, // ) -> Promise<Map> const _mapMapSeriesAsync = async function (iterator, f, result) { let iteration = iterator.next() while (!iteration.done) { let resultItem = f(iteration.value[1]) if (isPromise(resultItem)) { resultItem = await resultItem } result.set(iteration.value[0], resultItem) iteration = iterator.next() } return result } const mapMapSeries = function (map, f) { const result = new Map() const iterator = map[symbolIterator]() let iteration = iterator.next() while (!iteration.done) { const key = iteration.value[0] const resultItem = f(iteration.value[1]) if (isPromise(resultItem)) { return resultItem.then(funcConcat( curry3(mapSet, result, key, __), thunkify3(_mapMapSeriesAsync, iterator, f, result), )) } result.set(key, resultItem) iteration = iterator.next() } return result } const tapSync = func => function tapping(...args) { func(...args) return args[0] } const promiseRace = Promise.race.bind(Promise) const arrayMapPoolAsync = async function ( array, f, concurrencyLimit, result, index, promises, ) { const arrayLength = array.length while (++index < arrayLength) { if (promises.size >= concurrencyLimit) { await promiseRace(promises) } const resultItem = f(array[index]) if (isPromise(resultItem)) { const selfDeletingPromise = resultItem.then( tapSync(() => promises.delete(selfDeletingPromise))) promises.add(selfDeletingPromise) result[index] = selfDeletingPromise } else { result[index] = resultItem } } return promiseAll(result) } const arrayMapPool = function (array, concurrency, f) { const arrayLength = array.length, result = Array(arrayLength) let index = -1 while (++index < arrayLength) { const resultItem = f(array[index]) if (isPromise(resultItem)) { const promises = new Set(), selfDeletingPromise = resultItem.then( tapSync(() => promises.delete(selfDeletingPromise))) promises.add(selfDeletingPromise) result[index] = selfDeletingPromise return arrayMapPoolAsync( array, f, concurrency, result, index, promises) } result[index] = resultItem } return result } const stringMapPool = function (s, concurrency, f) { const result = arrayMapPool(s, concurrency, f) return isPromise(result) ? result.then(curry3(callPropUnary, __, 'join', '')) : result.join('') } const _setMapPoolAsync = async function ( s, iterator, concurrency, f, result, promises, ) { let iteration = iterator.next() while (!iteration.done) { if (promises.size >= concurrency) { await promiseRace(promises) } const resultItem = f(iteration.value, iteration.value, s) if (isPromise(resultItem)) { const selfDeletingPromise = resultItem.then(resolvedValue => { promises.delete(selfDeletingPromise) result.add(resolvedValue) }) promises.add(selfDeletingPromise) } else { result.add(resultItem) } iteration = iterator.next() } if (promises.size > 0) { await promiseAll(promises) } return result } const setMapPool = function (s, concurrency, f) { const result = new Set() const iterator = s[symbolIterator]() let iteration = iterator.next() while (!iteration.done) { const resultItem = f(iteration.value, iteration.value, s) if (isPromise(resultItem)) { const promises = new Set() const selfDeletingPromise = resultItem.then(resolvedValue => { promises.delete(selfDeletingPromise) result.add(resolvedValue) }) promises.add(selfDeletingPromise) return _setMapPoolAsync(s, iterator, concurrency, f, result, promises) } result.add(resultItem) iteration = iterator.next() } return result } const _mapMapPoolAsync = async function ( m, iterator, concurrency, f, result, promises, ) { let iteration = iterator.next() while (!iteration.done) { if (promises.size >= concurrency) { await promiseRace(promises) } const key = iteration.value[0] const resultItem = f(iteration.value[1], key, m) if (isPromise(resultItem)) { result.set(key, resultItem) const selfDeletingPromise = resultItem.then(resolvedValue => { promises.delete(selfDeletingPromise) result.set(key, resolvedValue) }) promises.add(selfDeletingPromise) } else { result.set(key, resultItem) } iteration = iterator.next() } if (promises.size > 0) { await promiseAll(promises) } return result } const mapMapPool = function (m, concurrency, f) { const result = new Map() const iterator = m[symbolIterator]() let iteration = iterator.next() while (!iteration.done) { const key = iteration.value[0] const resultItem = f(iteration.value[1], key, m) if (isPromise(resultItem)) { const promises = new Set() result.set(key, resultItem) const selfDeletingPromise = resultItem.then(resolvedValue => { promises.delete(selfDeletingPromise) result.set(key, resolvedValue) }) promises.add(selfDeletingPromise) return _mapMapPoolAsync(m, iterator, concurrency, f, result, promises) } result.set(key, resultItem) iteration = iterator.next() } return result } const _objectMapPoolAsync = async function ( o, concurrency, f, result, doneKeys, promises, ) { for (const key in o) { if (key in doneKeys) { continue } if (promises.size >= concurrency) { await promiseRace(promises) } const resultItem = f(o[key], key, o) if (isPromise(resultItem)) { result[key] = resultItem const selfDeletingPromise = resultItem.then(resolvedValue => { promises.delete(selfDeletingPromise) result[key] = resolvedValue }) promises.add(selfDeletingPromise) } else { result[key] = resultItem } } if (promises.size > 0) { await promiseAll(promises) } return result } const objectMapPool = function (o, concurrency, f) { const result = {} const doneKeys = {} for (const key in o) { doneKeys[key] = true const resultItem = f(o[key], key, o) if (isPromise(resultItem)) { const promises = new Set() result[key] = resultItem const selfDeletingPromise = resultItem.then(resolvedValue => { promises.delete(selfDeletingPromise) result[key] = resolvedValue }) promises.add(selfDeletingPromise) return _objectMapPoolAsync(o, concurrency, f, result, doneKeys, promises) } result[key] = resultItem } return result } const _curryArity = (arity, func, args) => function curried(...curriedArgs) { const argsLength = args.length, curriedArgsLength = curriedArgs.length, nextArgs = [] let argsIndex = -1, curriedArgsIndex = -1, numCurriedPlaceholders = 0 while (++argsIndex < argsLength) { const arg = args[argsIndex] if (arg == __ && (curriedArgsIndex += 1) < curriedArgsLength) { const curriedArg = curriedArgs[curriedArgsIndex] if (curriedArg == __) { numCurriedPlaceholders += 1 } nextArgs.push(curriedArg) } else { nextArgs.push(arg) } if (nextArgs.length == arity) { return numCurriedPlaceholders == 0 ? func(...nextArgs) : curryArity(arity, func, nextArgs) } } while (++curriedArgsIndex < curriedArgsLength) { const curriedArg = curriedArgs[curriedArgsIndex] if (curriedArg == __) { numCurriedPlaceholders += 1 } nextArgs.push(curriedArg) if (nextArgs.length == arity) { return numCurriedPlaceholders == 0 ? func(...nextArgs) : curryArity(arity, func, nextArgs) } } return curryArity(arity, func, nextArgs) } const curryArity = function (arity, func, args) { const argsLength = args.length if (argsLength < arity) { return _curryArity(arity, func, args) } let argsIndex = -1 while (++argsIndex < argsLength) { const arg = args[argsIndex] if (arg == __) { return _curryArity(arity, func, args) } } return func(...args) } const spread2 = func => function spreading2([arg0, arg1]) { return func(arg0, arg1) } const objectMapEntries = function (object, mapper) { const result = {}, promises = [] for (const key in object) { const value = object[key], mapping = mapper([key, value]) if (isPromise(mapping)) { promises.push(mapping.then( spread2(curryArity(3, objectSet, [result])))) } else { result[mapping[0]] = mapping[1] } } return promises.length == 0 ? result : promiseAll(promises).then(always(result)) } // (mapper function, result Map, promises Array<Promise>) => (key any, value any) => () const mapMapEntriesForEachCallback = ( mapper, result, promises, ) => function callback(value, key) { const mapping = mapper([key, value]) if (isPromise(mapping)) { promises.push(mapping.then(spread2(curryArity(3, mapSet, [result])))) } else { result.set(mapping[0], mapping[1]) } } const mapMapEntries = function (source, mapper) { const result = new Map(), promises = [] source.forEach(mapMapEntriesForEachCallback(mapper, result, promises)) return promises.length == 0 ? result : promiseAll(promises).then(always(result)) } const _map = function (value, mapper) { if (isArray(value)) { return arrayMap(value, mapper) } if (value == null) { return value } if (typeof value.then == 'function') { return value.then(mapper) } if (typeof value.map == 'function') { return value.map(mapper) } if (typeof value == 'string' || value.constructor == String) { return stringMap(value, mapper) } if (value.constructor == Set) { return setMap(value, mapper) } if (value.constructor == Map) { return mapMap(value, mapper) } if (typeof value[symbolIterator] == 'function') { return MappingIterator(value[symbolIterator](), mapper) } if (typeof value[symbolAsyncIterator] == 'function') { return MappingAsyncIterator(value[symbolAsyncIterator](), mapper) } if (value.constructor == Object) { return objectMap(value, mapper) } return mapper(value) } const map = function (arg0, arg1) { if (arg1 == null) { return curry2(_map, __, arg0) } return isPromise(arg0) ? arg0.then(curry2(_map, __, arg1)) : _map(arg0, arg1) } // _mapEntries(value Object|Map, mapper function) -> Object|Map const _mapEntries = (value, mapper) => { if (value == null) { throw new TypeError('value is not an Object or Map') } if (value.constructor == Object) { return objectMapEntries(value, mapper) } if (value.constructor == Map) { return mapMapEntries(value, mapper) } throw new TypeError('value is not an Object or Map') } map.entries = function mapEntries(arg0, arg1) { if (arg1 == null) { return curry2(_mapEntries, __, arg0) } return isPromise(arg0) ? arg0.then(curry2(_mapEntries, __, arg1)) : _mapEntries(arg0, arg1) } const _mapSeries = function (collection, f) { if (isArray(collection)) { return arrayMapSeries(collection, f) } if (collection == null) { throw new TypeError(`invalid collection ${collection}`) } if (typeof collection == 'string' || collection.constructor == String) { return stringMapSeries(collection, f) } if (collection.constructor == Set) { return setMapSeries(collection, f) } if (collection.constructor == Map) { return mapMapSeries(collection, f) } if (collection.constructor == Object) { return objectMapSeries(collection, f) } throw new TypeError(`invalid collection ${collection}`) } map.series = function mapSeries(arg0, arg1) { if (arg1 == null) { return curry2(_mapSeries, __, arg0) } return isPromise(arg0) ? arg0.then(curry2(_mapSeries, __, arg1)) : _mapSeries(arg0, arg1) } const _mapPool = function (collection, concurrency, f) { if (isArray(collection)) { return arrayMapPool(collection, concurrency, f) } if (collection == null) { throw new TypeError(`invalid collection ${collection}`) } if (typeof collection == 'string' || collection.constructor == String) { return stringMapPool(collection, concurrency, f) } if (collection.constructor == Set) { return setMapPool(collection, concurrency, f) } if (collection.constructor == Map) { return mapMapPool(collection, concurrency, f) } if (collection.constructor == Object) { return objectMapPool(collection, concurrency, f) } throw new TypeError(`invalid collection ${collection}`) } map.pool = function mapPool(arg0, arg1, arg2) { if (arg2 == null) { return curry3(_mapPool, __, arg0, arg1) } return isPromise(arg0) ? arg0.then(curry3(_mapPool, __, arg1, arg2)) : _mapPool(arg0, arg1, arg2) } export default map