✅ 목표

  • 객체를 이터러블 프로그래밍으로 작업한다는 것은 어떤 의미 일까?

👋 L.values() 구현

기존 values()

  • Object.values는 즉시 배열로 만들어 놓고 시작함
const obj1 = {
  a: 1,
  b: 2,
  c: 3,
  d: 4,
};

console.log(Object.values(obj1));

L.values()

  • 필요한 만큼만 배열로 만들어서, 추출해 사용(코드 평가를 지연)
  • 즉, 밑에 take에서 2개만 꺼내기 때문에 obj1에서 2개만 배열로 만들어서 사용
  • 또한, 객체 값이 많아 지거나, 로직이 복잡할 경우에는 밑에 코드가 위의 코드보다 유리함
const obj1 = {
  a: 1,
  b: 2,
  c: 3,
  d: 4,
};

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

_.go(
  obj1,
  L.values,
  L.map((a) => a + 10),
  L.take(2),
  _.reduce((a, b) => a + b),
  console.log,
);

👋 L.entries() 구현

  • 지연적인 함수를 구현하게 되면, 어떤 값이 들어오던지 간에 이터러블 프로그래밍 가능
L.entries = function* (obj) {
  for (const k in obj) {
    yield [k, obj[k]];
  }
};

_.go(
  obj1,
  L.entries,
  L.filter(([_, v]) => v % 2),
  L.map(([k, v]) => ({ [k]: v })),
  _.reduce(Object.assign),
  console.log,
);

👋 L.keys() 구현

L.keys = function* (obj) {
  for (const k in obj) {
    yield k;
  }
};

_.go(obj1, L.keys, _.each(console.log));

👋 어떠한 값이든 이터러블 프로그래밍으로 다루기

  • 이터러블로 이터러블 프로그래밍 가능
  • 객체를 제너레이터를 이용해, 이터레이터로 만들어서 이터러블 프로그래밍 가능
  • 어떤 제너레이터든 이터레이터로 만들어서 이터러블 프로그래밍 가능
const g1 = function* (stop) {
  let i = -1;
  while (++i < stop) {
    yield 10;
    if (false) yield 20 + 30;
    yield 30;
  }
};

// L.take로 어디까지 평가할 것인지 컨트롤 가능
console.log([...L.take(3, g1(10))]);

_.each(console.log, g1(10));

_.go(
  g1(10),
  L.take(2),
  _.reduce((a, b) => a + b),
  console.log,
);

👋 object 함수 만들기

  • 다형성이 넓음
const a = [
  ['a', 1],
  ['b', 2],
  ['c', 3],
];
const b = { a: 1, b: 2, c: 3 };

const object = (entries) =>
  _.go(
    entries,
    L.map(([k, v]) => ({ [k]: v })),
    _.reduce(Object.assign),
  );

console.log(object(L.entries({ b: 2, c: 3 })));

reduce하나로 Object 함수 만들기

const a = [
  ['a', 1],
  ['b', 2],
  ['c', 3],
];
const b = { a: 1, b: 2, c: 3 };

const object = (entries) => _.reduce((obj, [k, v]) => ((obj[k] = v), obj), {}, entries);

console.log(object(L.entries({ b: 2, c: 3 })));

참고

  • Map 을 통해 m을 만든후, JSON.stringify() 하게되면 값이 사라짐
  • 예를 들어, Map을 통해 오브젝트로 서버에게 전달할려면, 오브젝트로 만들어야 함
let m = new Map();
m.set('a', 10);
m.set('b', 20);
m.set('c', 30);

JSON.stringify(m); // "{}"
  • 밑에 코드에서 m 이 이터러블을 지원하기 때문에, object함수에서는 m값이 사라지지 않음
  • 왜냐하면, m 안에 있는 코드를 평가하면 밑에 처럼 배열 값이 존재함
  • 또한, Map함수로 만들 경우 values(), keys() 함수를 지원함
const object = (entries) => _.reduce((obj, [k, v]) => ((obj[k] = v), obj), {}, entries);

let m = new Map();

[...m[Symbol.iterator]()]; // [["a", 10], ["b", 20], ["c", 30]]

m.set('a', 10);
m.set('b', 20);
m.set('c', 30);

console.log(object(m)); // {a: 10, b: 20, c: 30}

👋 mapObject 함수 만들기

결과

console.log(mapObject((a) => a + 10, { a: 1, b: 2, c: 3 }));

// 결과
// { a: 11, b: 12, c: 13 }

함수형적 사고

  • 먼저 객체를 받아서, entries 함수 결과처럼 배열로 만들어서 이터러블로 다룸
console.log(mapObject(a => a + 10, { a: 1, b: 2, c: 3 }));

[['a', 1], ['b', 2], ['c', 3]]

[['a', 11], ['b', 12], ['c', 13]]

{ a: 11 } ...

{ a: 11, b: 12, c: 13 }

mapObject 함수

const object = (entries) => _.reduce((obj, [k, v]) => ((obj[k] = v), obj), {}, entries);

const mapObject = (f, obj) =>
  _.go(
    obj,
    L.entries,
    _.map(([k, v]) => [k, f(v)]),
    object,
  );

console.log(mapObject((a) => a + 10, { a: 1, b: 2, c: 3 }));

// 결과
// { a: 11, b: 12, c: 13 }

👋 pick 함수 만들기

결과

const obj2 = { a: 1, b: 2, c: 3, d: 4, e: 5 };

console.log(pick(['b', 'c', 'z'], obj2));
// { b: 2, c: 3 }

pick 함수

  • 사용하는 쪽에 맞게 함수를 만드는 것이 좋음
  • 즉, pick에 들어오는 인자를 순회하면서 해당 값을 추출
// 파이프라인 표현
const pick = (ks, obj) =>
  _.go(
    ks,
    _.map((k) => [k, obj[k]]),
    object,
  );

// 다른 표현
const pick = (ks, obj) => object(_.map((k) => [k, obj[k]], ks));

Undefined 제거

  • 런타임 즉, 코드를 실행하는 과정에서 결과가 undefined 값이 나오지 않도록 해야 좋음
  • 예를 들어 서버에 const data = {a: 2, b: 3, c: undefined} 값을 전달 할 때
  • JSON.stringify(data) 처리 하게 되면 “{a: 2, b: 3}" 로 처리가 됨
  • 즉, 서버에서 처리할 수도 없고 프론트에서도 값을 받을 수도 없음
  • 단, 로직 상에서 undefined을 처리하는 것은 괜찮음
const pick = (ks, obj) =>
  _.go(
    ks,
    L.map((k) => [k, obj[k]]),
    L.reject(([k, v]) => v === undefined),
    object,
  );

👋 indexBy 함수 만들기

Find 함수를 통해 특정 값 추출

  • Find 함수를 통해서 특정 값을 추출할려면, 모든 값을 순회해야 됨
const users = [
  { id: 5, name: 'AA', age: 35 },
  { id: 10, name: 'BB', age: 26 },
  { id: 19, name: 'CC', age: 28 },
  { id: 23, name: 'CC', age: 34 },
  { id: 24, name: 'EE', age: 23 },
];

// 사용
_.find((u) => u.id == 19, users);

indexBy 함수를 통해 특정값 추출

  • 순회하지 않고, 바로 index로 값을 추출
  • 즉, find보다 성능이 좋음
const users = [
  { id: 5, name: 'AA', age: 35 },
  { id: 10, name: 'BB', age: 26 },
  { id: 19, name: 'CC', age: 28 },
  { id: 23, name: 'CC', age: 34 },
  { id: 24, name: 'EE', age: 23 }
];

// 무언가 새로운 이터레이터를 만들기 위해서는 reduce가 필요
_.indexBy = (f, iter) =>
  _.reduce((obj, a) => (obj[f(a)] = a, obj), {}, iter);

_.indexBy(u => u.id, users);

// 결과
{
	5: { id: 5, name: 'AA', age: 35 },
	10: { id: 10, name: 'BB', age: 26 },
	19: { id: 19, name: 'CC', age: 28 },
	23: { id: 23, name: 'CC', age: 34 },
	24: { id: 24, name: 'EE', age: 23 },
}

👋 indexBy 된 값을 filter 하기

  • 기존 indexBy 결과는 이터러블이 아니기 때문에 순회가 되지 않아 필터처리가 힘듬
const users = [
  { id: 5, name: 'AA', age: 35 },
  { id: 10, name: 'BB', age: 26 },
  { id: 19, name: 'CC', age: 28 },
  { id: 23, name: 'CC', age: 34 },
  { id: 24, name: 'EE', age: 23 },
];

_.indexBy = (f, iter) => _.reduce((obj, a) => ((obj[f(a)] = a), obj), {}, iter);

const user2 = _.indexBy((u) => u.id, users);

const users3 = _.go(
  users2,
  L.entries,
  L.filter(([_, { age }]) => age < 30),
  L.take(2),
  object,
);

console.log(users3[19]);


👍 느낀점

JS에서 제공하는 Object 내부 함수를 지연적인 함수형 프로그래밍으로 만드는 학습을 통해, 지연적 코드 평가에 대한 이해하는 시간을 가졌습니다.


참고

유인동님의 함수형 프로그래밍과 JS ES6+ 강의