Frontend/JS

[JavaScript] 함수 합성을 통해 find 함수 만들기

턴태 2023. 5. 8. 22:23

앞선 강의 내용

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

const join = curry((sep = ',', iter) =>
  reduce((a, b) => `${a}${sep}${b}`, iter));
  
const queryStr = pipe(
  L.entries,
  L.map(([k, v]) => `${k}=${v}`),
  join('&')
);

console.log(queryStr({ limit: 10, offset: 10, type: 'notice' }));

앞서 배웠던 join 함수는 reduce 계열의 함수라고 볼 수 있다. 즉, reduce로 만들 수 있는 함수인 것이다.

 

entries의 경우는 map을 통해서 만들어지는 함수라고 볼 수 있다.

 

함수형 프로그래밍에서는 유사한 계열을 가지도록 함수를 만들 수 있는데, 앞으로 만들어볼 find라는 함수는 take를 통해서 만들 수 있다.

take, find 함수

find 함수를 만들기에 앞서 아래와 같은 데이터가 있다고 가정해보자

const users = [
  { age: 32 },
  { age: 31 },
  { age: 37 },
  { age: 28 },
  { age: 25 },
  { age: 32 },
  { age: 31 },
  { age: 37 },
];

이 users 객체 배열에서 원하는 값만 추출하고자 할 때 사용할 수 있는 것이 find 함수이다.

 

const find = (f, iter) => go(
  iter,
  filter(f)
);

console.log(find(u => u.age < 30, users));

// (2) [{...}, {...}]
//   0: {age: 28}
//   1: {age: 25}
//   length: 2
//   __proto__: Array(0)

지금은 조건을 일치하는 모든 값을 뽑아내고 있다. 하지만, find 함수는 하나의 값만 뽑아내는 함수이기 때문에 아래와 같이 하나만 추출하도록 한다.

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

console.log(find(u => u.age < 30, users));

// {age: 28}

이때, 함수가 내부적으로 어떻게 동작하는지 확인해보면 아래와 같다.

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

console.log(find(u => u.age < 30, users));

// {age: 32}
// {age: 31}
// {age: 37}
// {age: 28}
// {age: 25}
// {age: 32}
// {age: 31}
// {age: 37}
// (2) [{...}, {...}]
// {age: 28}

여기서 인자로 이터러블한 값을 받기 때문에 지연평가를 수행할 수 있다.

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

console.log(find(u => u.age < 30, users));

함수형 프로그래밍의 장점을 다시 확인할 수 있는데, 언젠가 다시 이 함수를 보게 됐을 때, 함수명을 통해 어떻게 흐름이 진행될지를 파악할 수 있기 때문에 가독성이 좋다.

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

console.log(find(u => u.age < 30)(users));

여기에 currying하여 위처럼 표현할 수도 있다. 함수를 정의하고 나중에 users를 인자로 받아 실행할 수 있다. 즉, 위를 만족하기 때문에 go 함수 내에서 아래와 같이 사용할 수도 있다.

go(users,
  L.map(u => u.age),
  find(n => n < 30),
  console.log);

go랑 curry가 너무 좋다. 일단 가독성이 매우 향상된다는 점이 큰 메리트인 것 같다. 매번 백엔드 코드를 다시 읽을 때면, 절차를 모두 이해해야 했는데, 이렇게 함수와 함수가 체이닝되어 사용되다보니 그 함수명을 통해서 어떤 흐름인지 확실하게 파악할 수 있어서 좋았다. 물론 아직 함수형 프로그래밍의 시작 단계이지만, 왜 함수형 프로그래밍이 각광받는지 알 수 있을 것 같다. 다만, 같은 스터디원분께서 한 가지 제언한 부분이 있는데, 협업의 과정에서 바로 실무에 적용해도 괜찮냐는 것이었다. 이는 처음에 go, curry를 이해하지 못한 내 모습을 되돌이켜보면서 탄식이 나오는 순간이었다. 아무리 효율적인 코드더라도 팀원들과 align을 맞추지 않은 상태이면 과연 그것 또한 좋은 방법론일지는 미지수다. 매번 좋은 방법이 등장했을 때 한 번 더 의심해보고 받아들이는 습관을 기르자.

 

출처: 인프런 함수형 프로그래밍과 JavaScript ES6+