[ FxTS ] 근-본 reduce 분석하기

UI SEOK YU·2023년 5월 30일
1

TypeScript

목록 보기
3/4
post-thumbnail

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는 인자의 개수에 따라 커링을 지원하며,
이터러블만 주어질 경우, 알아서 초기값을 뽑아 계산한다.
또한 비동기 요소가 있을 경우, 그 프로미스를 해결한 후 값을 넘기는 형태로 진행된다.

0개의 댓글