fxts.reduce
: https://github.com/marpple/FxTS/blob/main/src/reduce.ts
FxTS 에서 reduce는 pipe 등 다른 근본적인 고차함수를 구성하는데 중요한 역할을 하기에,
어떻게 동작하는지 알아야 할 필요가 있었다. (특히 비동기를 다룰 때)
하지만 깃헙에 올라와 있는 코드를 그대로 보면서 분석하려고 하니 눈에 잘 들어오지도 않고 타입도 너무 많아 읽기 헷갈렸다. 따라서 동작방식 위주로 기록하며 분석했다.
import { isAsyncIterable, isIterable } from "./_internal/utils";
import pipe1 from "./pipe1";
import type Arrow from "./types/Arrow";
import type IterableInfer from "./types/IterableInfer";
import type ReturnValueType from "./types/ReturnValueType";
function sync<A, B>(f: (a: B, b: A) => B, acc: B, iterable: Iterable<A>): B {
for (const a of iterable) {
acc = f(acc, a);
}
return acc;
}
async function async<A, B>(
f: (a: B, b: A) => B,
acc: Promise<B>,
iterable: AsyncIterable<A>,
) {
for await (const a of iterable) {
// becauseof using es5, use `await`
acc = await pipe1(acc, (acc) => f(acc as B, a));
}
return acc;
}
/**
* Also known as foldl, this method boils down a list of values into a single value.
*
* @example
* ```ts
* const sum = (a:number, b:number) => a + b;
* reduce(sum, [1, 2, 3, 4]); // 10
* reduce(sum, 0, [1, 2, 3, 4]); // 10
*
* // with pipe
* pipe(
* [1, 2, 3, 4],
* map(a => a + 10),
* filter(a => a % 2 === 0),
* reduce(sum),
* ); // 26
*
* await pipe(
* Promise.resolve([1, 2, 3, 4]),
* map((a) => a + 10),
* filter(a => a % 2 === 0),
* reduce(sum),
* ); // 26
*
* // if you want to use asynchronous callback
* await pipe(
* Promise.resolve([1, 2, 3, 4]),
* toAsync,
* map(async (a) => a + 10),
* filter(a => a % 2 === 0),
* reduce(sum),
* ); // 26
*
* // with toAsync
* await pipe(
* [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3), Promise.resolve(4)],
* toAsync,
* map(a => a + 10),
* filter(a => a % 2 === 0),
* reduce(sum),
* ); // 26
* ```
*
* {@link https://codesandbox.io/s/fxts-reduce-tf56j | Try It}
*
* see {@link https://fxts.dev/docs/pipe | pipe}, {@link https://fxts.dev/docs/toAsync | toAsync},
* {@link https://fxts.dev/docs/map | map}, {@link https://fxts.dev/docs/filter | filter}
*/
function reduce<A extends readonly []>(f: Arrow, iterable: A): undefined;
function reduce<A extends readonly [], B>(f: Arrow, seed: B, iterable: A): B;
function reduce<A>(f: (a: A, b: A) => A, iterable: Iterable<A>): A;
function reduce<A, B>(f: (a: B, b: A) => B, iterable: Iterable<A>): B;
function reduce<A, B>(f: (a: B, b: A) => B, seed: B, iterable: Iterable<A>): B;
function reduce<A>(
f: (a: A, b: A) => A,
iterable: AsyncIterable<A>,
): Promise<A>;
function reduce<A, B>(
f: (a: B, b: A) => B | Promise<B>,
seed: B | Promise<B>,
iterable: AsyncIterable<A>,
): Promise<B>;
function reduce<A, B>(
f: (a: B, b: A) => B | Promise<B>,
iterable: AsyncIterable<A>,
): Promise<B>;
function reduce<A extends Iterable<unknown> | AsyncIterable<unknown>>(
f: (
a: IterableInfer<A>,
b: IterableInfer<A>,
) => IterableInfer<A> | Promise<IterableInfer<A>>,
): (iterable: A) => ReturnValueType<A, IterableInfer<A>>;
function reduce<A extends Iterable<unknown> | AsyncIterable<unknown>, B>(
f: (a: B, b: IterableInfer<A>) => B | Promise<B>,
): (iterable: A) => ReturnValueType<A, B>;
function reduce<A extends Iterable<unknown> | AsyncIterable<unknown>, B>(
f: (a: B, b: IterableInfer<A>) => B,
seed?: B | Iterable<IterableInfer<A>> | AsyncIterable<IterableInfer<A>>,
iterable?: Iterable<IterableInfer<A>> | AsyncIterable<IterableInfer<A>>,
):
| B
| undefined
| Promise<B | undefined>
| ((iterable: A) => ReturnValueType<A, B>) {
if (iterable === undefined) {
if (seed === undefined) {
return (iterable: A) =>
reduce(f, iterable as any) as ReturnValueType<A, B>;
}
if (isIterable(seed)) {
const iterator = seed[Symbol.iterator]();
const { done, value } = iterator.next();
if (done) {
return undefined;
}
return sync(f, value as B, {
[Symbol.iterator]() {
return iterator;
},
});
}
if (isAsyncIterable(seed)) {
const iterator = seed[Symbol.asyncIterator]();
return iterator.next().then(({ done, value }) => {
if (done) {
return undefined;
}
return async(f, value as Promise<B>, {
[Symbol.asyncIterator]() {
return iterator;
},
});
});
}
throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
}
if (isIterable(iterable)) {
return sync(f, seed as B, iterable);
}
if (isAsyncIterable(iterable)) {
return async(f, Promise.resolve(seed as B), iterable);
}
throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
}
export default reduce;
//--------------------------------------------------------------
function sync <A, B>
사용타입:
A
B
인자:
f: (a: B, b: A) => B,
acc: B,
iterable: Iterable<A>
본문:
//iterable을 받아서 acc와 함께 f 에 대입하는것을 반복
//우리가 아는 그 reduce 처럼 동작
async function async<A, B>
사용타입:
A
B
인자:
f: (a: B, b: A) => B,
acc: Promise<B>,
iterable: AsyncIterable<A>,
본문:
// iterable 돌려 await 하여 얻은 resolve된 a를, acc와 함께
// pipe1으로 함수를 실행시킨다.
// pipe는 acc가 프로미스이면 await했다가 함수를 실행시키는 고차함수이다.
// 따라서 async 함수는
// 시작 값이 promise이던,
// 이터레이터에서 나온 값이 프로미스이던,
// 함수를 거쳐 나온 값이 promise 이던 상관없이 resolve 값을 다룰 수 있다.
//--------------------------------------------------------------
function reduce
사용타입:
A extends (Iterable<unknown>|syncIterable<unknown>),
B
인자 :
(
f: (a: B,
b: IterableInfer<A>) => B,
seed?: B
| Iterable<IterableInfer<A>>
| AsyncIterable<IterableInfer<A>>,
iterable?: Iterable<IterableInfer<A>>
| AsyncIterable<IterableInfer<A>>,
)
리턴형태 :
B
| undefined
| Promise<B | undefined>
| ((iterable: A) => ReturnValueType<A, B>)
함수본문:
// 3번째 인자가 없을 때
if (iterable === undefined)
// 2번재 인자도 없을 때
if (seed === undefined)
// 이터러블을 받아서 리듀스 하는 함수 반환(커링)
(iterable: A) =>
reduce(f, iterable as any) as ReturnValueType<A, B>;
// 아래 두 경우는 원래 3번재 인자에 들어가야할 iterable이
// 현재 seed자리에 들어간거라 보면 된다.
// seed(첫 계산시작점) 이 iterable 하면
if (isIterable(seed))
// seed(원래iterator어야 했던 것)를 이터레이터로 만들고
// 이터레이터로부터 값을 받아
// 시작부터 끝난상태라면 undefined 반환
// 아니라면 해당 이터레이터를 이터레이터를 sync에서 for문으로 돌려줘야 하므로
// 다시 이터러블객체에 담아 sync 함수를 돌려 반환
// 비동기 이터러블일때
if (isAsyncIterable(seed))
// 이터러블을 이터레이터로 만들어서
// 값을 뽑아 (비동기 이터러블이니 프로미스일 것) 기다린후, value를 뽑는다.
// 이미 끝난경우 undefined 반환
// 아니라면 해당 이터레이터를 async에서 for문으로 돌려줘야 하므로
// 다시 이터러블객체에 담아 async에 넣고 돌려 반환
// 3개의 인자가 모두 정상적으로 주어졌을 때, 바로 sync나 async 실행
if (isIterable(iterable)) {
return sync(f, seed as B, iterable);
}
if (isAsyncIterable(iterable)) {
return async(f, Promise.resolve(seed as B), iterable);
}
fxts.reduce는 인자의 개수에 따라 커링을 지원하며,
이터러블만 주어질 경우, 알아서 초기값을 뽑아 계산한다.
또한 비동기 요소가 있을 경우, 그 프로미스를 해결한 후 값을 넘기는 형태로 진행된다.