# 제너레이터를 이용하여 시간 효율성 높은 함수 만들기

# range 함수

# 명령형

const range = l => {
  let i = -1;
  let res = [];
  while (++i < l) {
    res.push(i);
  }
  return res;
};

let list = range(50000);
log(reduce(add, list));

# 함수형

const L = {};
L.range = function*(l) {
  let i = -1;
  while (++i < l) {
    // log(lists.next()) 가 실행되어야 콘솔이 찍힘
    log(i, "L.range");
    yield i;
  }
};

const lists = L.range(50000);
log(lists); // L.range {<suspended>}
log(lists.next()); // 왼쪽 처럼 lists의 내부가 직접 로직에 포함되어야 순차적으로 하나씩 실행됨
log(reduce(add, lists));

# 차이

명령형에서는 list에 배열이 담기지만 함수형에서는 lists에 리터러블이 담기게 됩니다. 배열, 이터러블 둘다 for of를 사용 가능하다는 점은 공통점이지만, 명령형에 담긴 배열은 let list = range(50000);를 통해 list가 선언되자마자 length 5만의 배열이 생성된다는 되지만 const lists = L.range(50000)를 통해 생성된 리터버블은 5만개의 lenght가 바로 생성되는 것이 아니라, lists가 로직에 의해 루프를 돌때가 되어야 1개 씩 생성된다는 다른 점이 있습니다.

# take

const take = curry((l, iter) => {
  let res = [];
  for (const a of iter) {
    res.push(a);
    if (res.length === l) return res;
  }
  return res;
});

log(take(5, range(100)));

log(go(L.range(100), take(5), reduce(add)));
  • 이터러블이 있는 대상만 사용 가능하고, l만큼 잘라서 넣는다.

# L.map

  • 이전에 만든 map 함수를 제너레이터 형태로 만든다
L.map = function*(f, iter) {
  for (const a of iter) yield f(a);
};

var it = L.map(a => a * 10, [1, 2, 3]);
log(it.next()); // {value: 10, done: false}

# L.filter

  • iter의 length가 4일 때, 총 4번이 yield가 되는 것이 아니라 원하는 상황에서만 yield가 된다.
L.filter = function*(f, iter) {
  for (const a of iter) if (f(a)) yield a;
};

var its = L.filter(a => a > 2, [1, 2, 3, 4]);
log(its.next()); // 1번 할때, 걸러지는 것이 나옴 { value: 3, done: false }
log(its.next()); // { value: 4, done: false }
log(its.next()); // { value: undefined, done: true }

# 쿼리스트링 함수 만들기 (reduce로 데이터 산출)

//api params 쉽게만들기
const params = {limit: 10, type: notice, page: 1)

const queryStr = params => go(
	params,
	Object,entries,
	map([k, v] => `${k}=${v}`),
	reduce((a, b) => `${a}&${b}`)

const queryStr = pipe(
	Object,entries,
	map([k, v] => `${k}=${v}`),
	reduce((a, b) => `${a}&${b}`)

log(queryStr(params)); // limit=10&type=notice&page=1

# join (reduce로 만드는 함수)

Array join보다 다형성 높은 join함수 (array만 사용되는 join이 아닌 다른 값도 join 사용하기)

const join = curry(sep=",", iter) =>
	reduce((a, b) => `${a}${sep}${b}`, iter))

const queryStr = pipe(
	Object,entries,
	L.map([k, v] => `${k}=${v}`),
	join('&')
);
log(queryStr(params));

// 배열 아니여도 사용가능
function *a() {
	yield 10;
	yield 11;
	yield 12;
	yield 13;
}
log(join(' - ', a())) // 10 - 11 - 12 - 13
  • 함수형 프로그래밍을 할때는, pipe 사이의 함수를 조각내 재사용 높게 사용가능하다

  • join은 reduce를 쓰고, 이러터블 프로토콜을 사용한다

    • 이터러블 프로토콜을 따른 다는 것은 지연성을 가진다는 것이다
    • join에게 오는 값을 지연할수있다.
      • map을 쓰는경우 모든 값이 map을 돌아야만 join이 실행되나, L.map을 사용하는 경우 요소 1개당 join을 바로 실행할 수 있어, 지연성을 가짐으로 take 같은 함수 실행시 빠르게 함수종료할수있다.
  • Object.entries도 즉시 결과를 리턴하는 것 이므로 지연성을 가지게 만들수있다.

L.entries = function *(obj) {
	for (const k in obj) yield [k, obj[k]
}

const queryStr = pipe(
	L.entries,
	L.map([k, v] => `${k}=${v}`),
	join('&')
);
log(queryStr(params));

# find (take를 이용해 만드는 함수)

# 비지연성

const users = [{ age: 32 }, { age: 31 }, { age: 30 }, { age: 29 }, { age: 28 }];

const find = (f, iter) => go(iter, filter(f), take(1), ([a]) => a);

log(find(u => u.age < 30, users)); // {age: 29}

# 지연성

const find = curry((f, iter) => go(iter, L.filter(f), take(1), ([a]) => a));
log(find(u => u.age < 30)(users)); // {age: 29}

// find가 받는 첫번째 값이 이터러블이기 때문에 아래와 같은 코드도 가능하다
go(
  users,
  L.map(u => u.age),
  find(n => n < 30),
  log
);
// 29

# L.map, L.filter로 map, filter 함수 만들기

# take(Infinity)

  • 일부만 가져오는것이 아니라, map, filter에 들어오는 iter 전체를 리턴하는 함수
L.map = curry(function*(f, iter) {
  for (const a of iter) {
    yield f(a);
  }
});

const takeAll = take(Infinity);

// const map = curry((f, iter) => go(iter, L.map(f), takeAll));
// const map = curry((f, iter) => go(L.map(f, iter), takeAll));
const map = curry(pipe(L.map, takeAll));

log(map(a => a + 10, L.range(4)));

// ## L.filter + take로 filter 만들기

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

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

log(filter(a => a % 2, range(4)));

# L.flatten

  • nesting된 배열을 일차원 배열로 펼쳐주는 함수
L.flatten = function*(iter) {
  for (const a of iter) {
    if (isIterable(a)) for (const b of a) yield b;
    else yield a;
  }
};

var its = L.flatten([[1, 2, 3], 4, 5, [6, 7, 8]]);
log([...its]); // [1,2,3,4,5,6,7,8]

log(take(5, L.flatten([[1, 2, 3], 4, 5, [6, 7, 8]])); // [1,2,3,4,5]

# L.deepFlat

  • 여러번 nesting된 배열 일차원으로 펼치는 함수
L.deepFlat = function* f(iter) {
  for (const a of iter) {
    if (isIterable(a)) yield* f(a);
    else yield a;
  }
};
log([...L.deepFlat([1, [2, [3, [4, 5]]]])]); //[1,2,3,4,5]

# L.flatMap

  • map 이후, flatten
L.flatMap = curry(pipe(L.map, L.flatten));

var its = go(
  [
    [1, 2],
    [3, 4],
    [5, 6]
  ],
  L.flatMap(L.map(a => a * a)),
  takeAll
);

// [ 1, 4, 9, 16, 25, 36 ]

# 2차원 배열 예제

const arr = [
  [1, 2],
  [3, 4, 5],
  [6, 7, 8]
];

var its = go(
  arr,
  L.flatten,
  L.filter(a => a % 2),
  L.map(a => a * a),
  takeAll,
  reduce(add)
);
console.log(its); // 84

# 지연성, 실무중심 코드

var users = [
  {
    name: "a",
    age: 21,
    family: [
      { name: "a1", age: 53 },
      { name: "a2", age: 47 },
      { name: "a3", age: 16 },
      { name: "a4", age: 15 }
    ]
  },
  {
    name: "b",
    age: 24,
    family: [
      { name: "b1", age: 58 },
      { name: "b2", age: 51 },
      { name: "b3", age: 19 },
      { name: "b4", age: 22 }
    ]
  },
  {
    name: "c",
    age: 31,
    family: [
      { name: "c1", age: 64 },
      { name: "c2", age: 62 }
    ]
  },
  {
    name: "d",
    age: 20,
    family: [
      { name: "d1", age: 42 },
      { name: "d2", age: 42 },
      { name: "d3", age: 11 },
      { name: "d4", age: 7 }
    ]
  }
];

go(
  users,
  L.flatMap(u => u.family), // L.map(u => u.family), L.flatten,
  L.filter(u => u.age > 20),
  L.map(u => u.age),
  take(4),
  reduce(add),
  log
);
  • 객체지향 프로그래밍은 데이터를 설계한 후, 메소드를 데이터에 맞게 만드는 것이고,
  • 함수형 프로그래밍은 범용적인 메소드를 만든 후, 데이터를 이에 맞추는 것이다.
  • 위처럼, flatMap, map, filter 같은 범용적인 메소드를 작성후, 그 함수에 맞게 데이터를 끼우면 더 쉽게 코드를 작성할 수 있다.
  • 상황이 주어지면, map하고 filter하고, reduce한다라는 사고를 먼저하여 메소드의 순서를 정의하고, 데이터를 끼워 맞추는게 함수형 프로그래밍
최근변경일: 3/25/2024, 12:16:11 PM