UNPKG

rubico

Version:

[a]synchronous functional programming

822 lines (731 loc) 22.3 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 isArray = Array.isArray const objectValues = Object.values const objectProto = Object.prototype const nativeObjectToString = objectProto.toString const objectToString = value => nativeObjectToString.call(value) const generatorFunctionTag = '[object GeneratorFunction]' const isGeneratorFunction = value => objectToString(value) == generatorFunctionTag const asyncGeneratorFunctionTag = '[object AsyncGeneratorFunction]' const isAsyncGeneratorFunction = value => objectToString(value) == asyncGeneratorFunctionTag const __ = Symbol.for('placeholder') // 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 iteratorReduceAsync = async function ( iterator, reducer, result, ) { let iteration = iterator.next() if (iteration.done) { return result } while (!iteration.done) { result = reducer(result, iteration.value) if (isPromise(result)) { result = await result } iteration = iterator.next() } return result } const iteratorReduce = function (iterator, reducer, result) { let iteration = iterator.next() if (iteration.done) { return result } if (result === undefined) { result = iteration.value iteration = iterator.next() } while (!iteration.done) { result = reducer(result, iteration.value) if (isPromise(result)) { return result.then(curry3(iteratorReduceAsync, iterator, reducer, __)) } iteration = iterator.next() } return result } const asyncIteratorReduce = async function (asyncIterator, reducer, result) { let iteration = await asyncIterator.next() if (iteration.done) { return result } if (result === undefined) { result = iteration.value iteration = await asyncIterator.next() } while (!iteration.done) { result = await reducer(result, iteration.value) iteration = await asyncIterator.next() } return result } const symbolIterator = Symbol.iterator const symbolAsyncIterator = Symbol.asyncIterator // 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 curryArgs3 const curryArgs3ResolveArgs0 = ( baseFunc, arg1, arg2, ) => function args0Resolver(...args) { return baseFunc(args, arg1, arg2) } // argument resolver for curryArgs3 const curryArgs3ResolveArgs1 = ( baseFunc, arg0, arg2, ) => function arg1Resolver(...args) { return baseFunc(arg0, args, arg2) } // argument resolver for curryArgs3 const curryArgs3ResolveArgs2 = ( baseFunc, arg0, arg1, ) => function arg2Resolver(...args) { return baseFunc(arg0, arg1, args) } const curryArgs3 = function (baseFunc, arg0, arg1, arg2) { if (arg0 == __) { return curryArgs3ResolveArgs0(baseFunc, arg1, arg2) } if (arg1 == __) { return curryArgs3ResolveArgs1(baseFunc, arg0, arg2) } return curryArgs3ResolveArgs2(baseFunc, arg0, arg1) } // 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 arrayReduceAsync = async function ( array, reducer, result, index, ) { const length = array.length while (++index < length) { result = reducer(result, array[index], index, array) if (isPromise(result)) { result = await result } } return result } const arrayReduce = function (array, reducer, result) { const arrayLength = array.length let index = -1 if (result === undefined) { result = array[++index] } while (++index < arrayLength) { result = reducer(result, array[index], index, array) if (isPromise(result)) { return result.then(curry4(arrayReduceAsync, array, reducer, __, index)) } } return result } // argument resolver for curry5 const curry5ResolveArg0 = ( baseFunc, arg1, arg2, arg3, arg4, ) => function arg0Resolver(arg0) { return baseFunc(arg0, arg1, arg2, arg3, arg4) } // argument resolver for curry5 const curry5ResolveArg1 = ( baseFunc, arg0, arg2, arg3, arg4, ) => function arg1Resolver(arg1) { return baseFunc(arg0, arg1, arg2, arg3, arg4) } // argument resolver for curry5 const curry5ResolveArg2 = ( baseFunc, arg0, arg1, arg3, arg4, ) => function arg2Resolver(arg2) { return baseFunc(arg0, arg1, arg2, arg3, arg4) } // argument resolver for curry5 const curry5ResolveArg3 = ( baseFunc, arg0, arg1, arg2, arg4, ) => function arg3Resolver(arg3) { return baseFunc(arg0, arg1, arg2, arg3, arg4) } // argument resolver for curry5 const curry5ResolveArg4 = ( baseFunc, arg0, arg1, arg2, arg3, ) => function arg3Resolver(arg4) { return baseFunc(arg0, arg1, arg2, arg3, arg4) } const curry5 = function (baseFunc, arg0, arg1, arg2, arg3, arg4) { if (arg0 == __) { return curry5ResolveArg0(baseFunc, arg1, arg2, arg3, arg4) } if (arg1 == __) { return curry5ResolveArg1(baseFunc, arg0, arg2, arg3, arg4) } if (arg2 == __) { return curry5ResolveArg2(baseFunc, arg0, arg1, arg3, arg4) } if (arg3 == __) { return curry5ResolveArg3(baseFunc, arg0, arg1, arg2, arg4) } return curry5ResolveArg4(baseFunc, arg0, arg1, arg2, arg3) } const objectKeys = Object.keys const objectReduceAsync = async function (object, reducer, result, keys, index) { const keysLength = keys.length while (++index < keysLength) { const key = keys[index] result = reducer(result, object[key], key, object) if (isPromise(result)) { result = await result } } return result } const objectReduce = function (object, reducer, result) { const keys = objectKeys(object), keysLength = keys.length let index = -1 if (result === undefined) { result = object[keys[++index]] } while (++index < keysLength) { const key = keys[index] result = reducer(result, object[key], key, object) if (isPromise(result)) { return result.then(curry5(objectReduceAsync, object, reducer, __, keys, index)) } } return result } const mapReduceAsync = async function ( map, reducer, result, mapEntriesIter, ) { for (const [key, value] of mapEntriesIter) { result = reducer(result, value, key, map) if (isPromise(result)) { result = await result } } return result } const mapReduce = function (map, reducer, result) { const mapEntriesIter = map.entries() if (result === undefined) { const firstIteration = mapEntriesIter.next() if (firstIteration.done) { return result } result = firstIteration.value[1] } for (const [key, value] of mapEntriesIter) { result = reducer(result, value, key, map) if (isPromise(result)) { return result.then(curry4( mapReduceAsync, map, reducer, __, mapEntriesIter)) } } return result } const reducerConcat = ( reducerA, reducerB, ) => function pipedReducer(result, item) { const intermediate = reducerA(result, item) return isPromise(intermediate) ? intermediate.then(curry2(reducerB, __, item)) : reducerB(intermediate, item) } const genericReduce = function (collection, reducer, result) { if (isArray(collection)) { return arrayReduce(collection, reducer, result) } if (collection == null) { return result === undefined ? curry2(reducer, collection, __) : reducer(result, collection) } if (collection.constructor == Map) { return mapReduce(collection, reducer, result) } if (typeof collection[symbolIterator] == 'function') { return iteratorReduce( collection[symbolIterator](), reducer, result) } if (typeof collection[symbolAsyncIterator] == 'function') { return asyncIteratorReduce( collection[symbolAsyncIterator](), reducer, result) } if (typeof collection.reduce == 'function') { return collection.reduce(reducer, result) } if (typeof collection.chain == 'function') { return collection.chain(curry2(reducer, result, __)) } if (typeof collection.flatMap == 'function') { return collection.flatMap(curry2(reducer, result, __)) } if (collection.constructor == Object) { return objectReduce(collection, reducer, result) } return result === undefined ? curry2(reducer, collection, __) : reducer(result, collection) } const arrayPush = function (array, value) { array.push(value) return array } const FlatMappingIterator = function (iterator, flatMapper) { let buffer = [], bufferIndex = 0 return { [symbolIterator]() { return this }, next() { if (bufferIndex < buffer.length) { const value = buffer[bufferIndex] bufferIndex += 1 return { value, done: false } } const iteration = iterator.next() if (iteration.done) { return iteration } const monadAsArray = genericReduce( flatMapper(iteration.value), arrayPush, []) // this will always have at least one item if (monadAsArray.length > 1) { buffer = monadAsArray bufferIndex = 1 } return { value: monadAsArray[0], done: false, } }, } } const promiseRace = Promise.race.bind(Promise) const sleep = time => new Promise(resolve => { setTimeout(resolve, time) }) const FlatMappingAsyncIterator = function (asyncIterator, flatMapper) { const buffer = [], promises = new Set() return { isAsyncIteratorDone: false, [symbolAsyncIterator]() { return this }, toString() { return '[object FlatMappingAsyncIterator]' }, async next() { while ( !this.isAsyncIteratorDone || buffer.length > 0 || promises.size > 0 ) { if (!this.isAsyncIteratorDone) { const { value, done } = await asyncIterator.next() if (done) { this.isAsyncIteratorDone = done } else { const monad = flatMapper(value) if (isPromise(monad)) { const bufferLoading = monad.then(curry3(genericReduce, __, arrayPush, buffer)) const promise = bufferLoading.then(() => promises.delete(promise)) promises.add(promise) } else { const bufferLoading = genericReduce(monad, arrayPush, buffer) if (isPromise(bufferLoading)) { const promise = bufferLoading.then(() => promises.delete(promise)) promises.add(promise) } } } } if (buffer.length > 0) { return { value: buffer.shift(), done: false } } if (promises.size > 0) { await promiseRace([sleep(1000), ...promises]) } } return { value: undefined, done: true } }, } } const always = value => function getter() { return value } const getArg1 = (arg0, arg1) => arg1 const identity = value => value const promiseAll = Promise.all.bind(Promise) const funcConcatSync = ( funcA, funcB, ) => function pipedFunction(...args) { return funcB(funcA(...args)) } const asyncIteratorForEach = async function (asyncIterator, callback) { const promises = [] for await (const item of asyncIterator) { const operation = callback(item) if (isPromise(operation)) { promises.push(operation) } } return promises.length == 0 ? asyncIterator : promiseAll(promises).then(always(asyncIterator)) } const arrayFlatten = function (array) { const length = array.length, promises = [], result = [] let index = -1 while (++index < length) { const item = array[index] if (isArray(item)) { const itemLength = item.length let itemIndex = -1 while (++itemIndex < itemLength) { result.push(item[itemIndex]) } } else if (item == null) { result.push(item) } else if (typeof item.then == 'function') { promises.push(item.then(curry2(arrayPush, result, __))) } else if (typeof item[symbolIterator] == 'function') { for (const subItem of item) { result.push(subItem) } } else if (typeof item[symbolAsyncIterator] == 'function') { promises.push(asyncIteratorForEach( item[symbolAsyncIterator](), curry2(arrayPush, result, __))) } else if (typeof item.chain == 'function') { const monadValue = item.chain(identity) isPromise(monadValue) ? promises.push(monadValue.then(curry2(arrayPush, result, __))) : result.push(monadValue) } else if (typeof item.flatMap == 'function') { const monadValue = item.flatMap(identity) isPromise(monadValue) ? promises.push(monadValue.then(curry2(arrayPush, result, __))) : result.push(monadValue) } else if (typeof item.reduce == 'function') { const folded = item.reduce(funcConcatSync( getArg1, curry2(arrayPush, result, __)), null) isPromise(folded) && promises.push(folded) } else if (item.constructor == Object) { for (const key in item) { result.push(item[key]) } } else { result.push(item) } } return promises.length == 0 ? result : promiseAll(promises).then(always(result)) } 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 arrayFlatMap = function (array, flatMapper) { const monadArray = arrayMap(array, flatMapper) return isPromise(monadArray) ? monadArray.then(arrayFlatten) : arrayFlatten(monadArray) } 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 objectAssign = Object.assign const objectFlatten = function (object) { const promises = [], result = {}, resultAssign = curry2(objectAssign, result, __), resultAssignReducer = funcConcatSync(getArg1, resultAssign), getResult = () => result for (const key in object) { const item = object[key] if (item == null) { continue } else if (typeof item[symbolIterator] == 'function') { for (const monadItem of item) { objectAssign(result, monadItem) } } else if (typeof item[symbolAsyncIterator] == 'function') { promises.push( asyncIteratorForEach(item[symbolAsyncIterator](), resultAssign)) } else if (typeof item.chain == 'function') { const monadValue = item.chain(identity) isPromise(monadValue) ? promises.push(monadValue.then(resultAssign)) : objectAssign(result, monadValue) } else if (typeof item.flatMap == 'function') { const monadValue = item.flatMap(identity) isPromise(monadValue) ? promises.push(monadValue.then(resultAssign)) : resultAssign(monadValue) } else if (typeof item.reduce == 'function') { const folded = item.reduce(resultAssignReducer, null) isPromise(folded) && promises.push(folded) } else { objectAssign(result, item) } } return promises.length == 0 ? result : promiseAll(promises).then(getResult) } const objectFlatMap = function (object, flatMapper) { const monadObject = objectMap(object, flatMapper) return isPromise(monadObject) ? monadObject.then(objectFlatten) : objectFlatten(monadObject) } const callPropUnary = (value, property, arg0) => value[property](arg0) 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)) } const setFlatten = function (set) { const size = set.size, promises = [], result = new Set(), resultAddReducer = (_, subItem) => result.add(subItem), resultAdd = curry3(callPropUnary, result, 'add', __), getResult = () => result for (const item of set) { if (isArray(item)) { const itemLength = item.length let itemIndex = -1 while (++itemIndex < itemLength) { result.add(item[itemIndex]) } } else if (item == null) { result.add(item) } else if (typeof item[symbolIterator] == 'function') { for (const subItem of item) { result.add(subItem) } } else if (typeof item[symbolAsyncIterator] == 'function') { promises.push( asyncIteratorForEach(item[symbolAsyncIterator](), resultAdd)) } else if (typeof item.chain == 'function') { const monadValue = item.chain(identity) isPromise(monadValue) ? promises.push(monadValue.then(resultAdd)) : result.add(monadValue) } else if (typeof item.flatMap == 'function') { const monadValue = item.flatMap(identity) isPromise(monadValue) ? promises.push(monadValue.then(resultAdd)) : result.add(monadValue) } else if (typeof item.reduce == 'function') { const folded = item.reduce(resultAddReducer, null) isPromise(folded) && promises.push(folded) } else if (item.constructor == Object) { for (const key in item) { result.add(item[key]) } } else { result.add(item) } } return promises.length == 0 ? result : promiseAll(promises).then(getResult) } const setFlatMap = function (set, flatMapper) { const monadSet = setMap(set, flatMapper) return isPromise(monadSet) ? monadSet.then(setFlatten) : setFlatten(monadSet) } const funcConcat = ( funcA, funcB, ) => function pipedFunction(...args) { const intermediate = funcA(...args) return isPromise(intermediate) ? intermediate.then(funcB) : funcB(intermediate) } const arrayJoin = (array, delimiter) => array.join(delimiter) const arrayFlattenToString = funcConcat( arrayFlatten, curry2(arrayJoin, __, '')) const stringFlatMap = function (string, flatMapper) { const monadArray = arrayMap(string, flatMapper) return isPromise(monadArray) ? monadArray.then(arrayFlattenToString) : arrayFlattenToString(monadArray) } const _flatMap = function (value, flatMapper) { if (isArray(value)) { return arrayFlatMap(value, flatMapper) } if (value == null) { return flatMapper(value) } if (typeof value.then == 'function') { return value.then(flatMapper) } if (typeof value.next == 'function') { return symbolIterator in value ? FlatMappingIterator(value, flatMapper) : FlatMappingAsyncIterator(value, flatMapper) } if (typeof value.chain == 'function') { return value.chain(flatMapper) } if (typeof value.flatMap == 'function') { return value.flatMap(flatMapper) } const valueConstructor = value.constructor if (valueConstructor == Object) { return objectFlatMap(value, flatMapper) } if (valueConstructor == Set) { return setFlatMap(value, flatMapper) } if (typeof value == 'string' || valueConstructor == String) { return stringFlatMap(value, flatMapper) } return flatMapper(value) } const flatMap = (arg0, arg1) => { if (typeof arg0 == 'function') { return curry2(_flatMap, __, arg0) } return isPromise(arg0) ? arg0.then(curry2(_flatMap, __, arg1)) : _flatMap(arg0, arg1) } export default flatMap