함수형 프로그래밍 4주차

강석우's avatar
Oct 26, 2024
함수형 프로그래밍 4주차

지연 평가 + Promise - L.map, map, take

Promise의 호환성을 늘리기 위해 기존의 go 함수를 개조해 아래와 같이 만든다.

const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);

L.map

기존

L.map = curry(function* (f, iter) {
  for (const a of iter) {
    yield f(a);
  }
});

Promise 호환 가능

L.map = curry(function* (f, iter) {
  for (const a of iter) {
    yield go1(a, f);
  }
});

map, L.map과 take를 사용한 go

    go(
      [1, 2, 3],
      L.map(a => Promise.resolve(a + 10)),
      take(2),
      log);

    go(
      [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)],
      L.map(a => a + 10),
      take(2),
      log);

    go(
      [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)],
      L.map(a => Promise.resolve(a + 10)),
      take(2),
      log);

    go(
      [1, 2, 3],
      map(a => Promise.resolve(a + 10)),
      log);

    go(
      [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)],
      map(a => a + 10),
      log);

    go(
      [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)],
      map(a => Promise.resolve(a + 10)),
      log);

Kleisli composition

오류가 있을 수 있는 상황에서 함수 합성의 규칙으로 의도하지 않은 결과가 아닌 Promise 객체를 반환한다.

모나드

함수 합성 관점에서 Promise는 비동기 상황에서 함수 합성을 안전하게 하게 하는 도구

합성함수

f(g(x)) 이런 식으로 하나의 인자에 여러 함수가 실행되는 것

L.filter

기존

L.filter = curry(function* (f, iter) {
  for (const a of iter) {
    if (f(a)) yield a;
  }
});

Promise 호환 가능

const nop = Symbol('nop');

L.filter = curry(function* (f, iter) {
  for (const a of iter) {
    const b = go1(a, f);
    if (b instanceof Promise) yield b.then(b => b ? a : Promise.reject(nop));
    else if (b) yield a;
  }
});

활용하기

go([1, 2, 3, 4, 5, 6],
    L.map(a => Promise.resolve(a * a)),
    // L.map(a => a * a),
    filter(a => Promise.resolve(a % 2)),
    // L.map(a => a * a),
    /*L.map(a => {
      log(a);
      return a * a;
    }),
    L.map(a => {
      log(a);
      return a * a;
    }),*/
    // take(4),
    /*log*/);

reduce

기존

const reduce = curry((f, acc, iter) => {
  if (!iter) {
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  } else {
    iter = iter[Symbol.iterator]();
  }
  let cur;
  while (!(cur = iter.next()).done) {
    const a = cur.value;
    acc = f(acc, a);
  }
  return acc;
});

Promise 호환가능

const reduceF = (acc, a, f) =>
  a instanceof Promise ?
    a.then(a => f(acc, a), e => e == nop ? acc : Promise.reject(e)) :
    f(acc, a);

const reduce = curry((f, acc, iter) => {
  if (!iter) return reduce(f, head(iter = acc[Symbol.iterator]()), iter);

  iter = iter[Symbol.iterator]();
  return go1(acc, function recur(acc) {
    let cur;
    while (!(cur = iter.next()).done) {
      acc = reduceF(acc, cur.value, f);
      if (acc instanceof Promise) return acc.then(recur);
    }
    return acc;
  });
});

지연된 함수열을 병렬적으로 평가하기

  • 비동기 작업을 병렬적으로 처리한다.

  • 메모리, cpu를 많이 사용할 수 있으나 여러 연산을 한번에 처리하는 과정을 작업하기에 좋다.

  const C = {};

  function noop() {
  }

  const catchNoop = ([...arr]) =>
    (arr.forEach(a => a instanceof Promise ? a.catch(noop) : a), arr);

  C.reduce = curry((f, acc, iter) => iter ?
    reduce(f, acc, catchNoop(iter)) :
    reduce(f, catchNoop(acc)));

  C.take = curry((l, iter) => take(l, catchNoop(iter)));

  C.takeAll = C.take(Infinity);

  C.map = curry(pipe(L.map, C.takeAll));

  C.filter = curry(pipe(L.filter, C.takeAll));

  const delay1000 = a => new Promise(resolve => {
    console.log('hi~');
    setTimeout(() => resolve(a), 1000);
  });

  C.map(a => delay1000(a * a), [1, 2, 3, 4]).then(log);
  C.filter(a => delay1000(a % 2), [1, 2, 3, 4]).then(log);

즉시 실행함수를 사용하게 되면 모든 작업을 처리해준 뒤 다음단계로 넘어가게 되어 시간이 오래 걸리게 되는 반면 병렬작업으로 처리해주면

즉시 평가

지연 평가

병렬 평가

map, filter

L.map, L.filter

C.map, C.filter

모든 작업을 처리 후 다음단계

필요한 작업을 마친 뒤 다음단계

병렬로 모든 작업을 동시에

필요한 부분까지만 작업하고 나머지는 처리하지 않아 효율적으로 작업을 할 수 있다.

CPU와 메모리는 많이 사용할 수 있으나 많은 작업을 동시에 처리할 수 있다.

즉시 평가
const arr = [0, 1, 2, 3, 4, 5]
const result = arr.map(num => num + 10).filter(num => num % 2).slice(0, 2)
console.log(result) // [11, 13]
병렬 평가
const arr = [0, 1, 2, 3, 4, 5]
const result = _.take(2,
  L.filter(num => num % 2,
    L.map(num => num + 10, arr)))
console.log(result) // [11, 13]

즉시, 지연, Promise, 병렬적 조합하기

const delay500 = (a, name) => new Promise(resolve => {
    console.log(`${name}: ${a}`);
    setTimeout(() => resolve(a), 100);
  });

  console.time('');
  go([1, 2, 3, 4, 5, 6, 7, 8],
    L.map(a => delay500(a * a, 'map 1')),
    L.filter(a => delay500(a % 2, 'filter 2')),
    L.map(a => delay500(a + 1, 'map 3')),
    C.take(2),
    reduce(add),
    log,
    _ => console.timeEnd(''));

Share article

석우의 개발블로그