본문 바로가기
JavaScript

[FP&ES6+] 결과를 만드는 함수 - reduce

by _sweep 2021. 12. 27.

함수형 프로그래밍과 JavaScript ES6+ 강의를 듣고 정리한 내용입니다.

 

 

결과를 만드는 함수

map과 filter는 이터러블 안쪽의 값을 꺼내서 기본적인 값을 유지한 채로 새로운 값을 만든다.

반대로 reducetake는 이터러블 안쪽의 값을 꺼내는 것은 동일하나 값을 유지시키는 것이 아닌 연산이나 특정 방식으로 값을 깨뜨려 그 결과가 합쳐진 새로운 값을 만들어낸다.

즉, 결과를 만들어 낸다.

 

const reduce = curry((f, acc, iter) => {
  if (!iter) {
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }
  for (const a of iter) {
    acc = f(acc, a);
  }
  return acc;
});

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

 

 

✏️ 예제 - queryStr()

reduce 함수를 활용한 예제로 queryStr 함수를 만들어보자.

 

## 함수 설명

queryStr 함수는 객체로부터 url의 query string을 얻어내는 함수이다.

url의 query string이란 다음과 같다.

 

https://www.tistory.com/oauth/authorize?
  client_id={client-id}
  &redirect_uri={redirect-uri}
  &response_type=code

 

위 url은 티스토리의 open API에서 유저 인증을 위한 url이다.

 

const obj = {
    client_id: '123',
    redirect_uri: 'http://test.com',
    response_type: 'code',
  }

 

이러한 형식의 객체가 있다고 가정했을 때

obj 안에서 client_id, redirect_uri, response_type 의 값들을 뽑아

client_id={client-id}&redirect_uri={redirect-uri}&response_type=code 라는 문자열을 만든 뒤

https://www.tistory.com/oauth/authorize? 에 만들어진 query string을 붙여 요청을 보내면 된다.

 

다시 말해 지금 만들고자 하는 queryStr 함수는 obj가 주어졌을 때 client_id={client-id}&redirect_uri={redirect-uri}&response_type=code 을 만들어내는 함수이다.

 

 

## 함수 구현

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

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

// output
// limit=10&offset=10&type=notice

 

인자로 객체를 받은 후 go문으로 값을 위에서 아래로 넘긴다.

Object.entries 를 통해 객체 안에 담긴 key, value들을 [ key, value ]의 배열로 만든다.

이후 map에서 각 요소들의 key와 value를 사용하여 key=value의 문자열로 만든 후

reduce에서 이 요소들을 모두 합치는데 구분자로 &을 사용한다.

 

이 queryStr 함수 구현에서는 obj의 값을 받아 go문의 첫 번째 인자로 obj를 그대로 넘기고 있다.

따라서 pipe문으로 중복을 줄일 수 있다.

 

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

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

// output
// limit=10&offset=10&type=notice

 

 

 

✏️ 예제 - join()

위의 queryStr 함수 구현에서 reduce 함수가 하는 일은 Array.prototype.join()과 비슷하다.

 

## 함수 설명

Array.prototype.join()

배열의 모든 요소를 연결해 하나의 문자열로 만든다.

arr.join([separator])
  • separator : 배열의 각 요소를 구분할 문자열을 지정한다. 생략시 쉼표로 구분된다. 빈 문자열일 경우 모든 요소들이 공백없이 연결된다.

Array.prototype.join()은 배열에서만 사용이 가능하다.

하지만 reduce를 이용해 join()을 만든다면 일반 배열 뿐만 아니라 위의 queryStr에서 사용한 것처럼 이터러블에는 모두 사용할 수 있다.

즉, 지금 구현할 join 함수는 Array.prototype.join()보다 다형성이 높다.

 

 

## 함수 구현

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

 

구분자인 separator와 이터러블 iter를 인자로 받아 iter의 각 요소들을 구분자로 연결한다.

 

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

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

// output
// limit=10&offset=10&type=notice

 

위의 queryStr 구현에서 reduce 대신 같은 동작을 하는 join을 넣은 모습이다.

 

 

## 다형성

function* a() {
    yield 1;
    yield 2;
    yield 3;
}

console.log(a().join('-')); // Error!
console.log(join('-', a()));

// output
// 1-2-3

 

join은 reduce를 기반으로 만들어졌기 때문에 위의 콘솔 로그에서 기본적으로 주어지는 Array.prototype.join()을 사용하려 했을 때는 에러가 나지만 새로 구현한 join은 에러가 나지 않고 원하는 대로 동작하는 것을 확인할 수 있다.

 

 

 

 지연 평가

queryStr과 join은 모두 reduce를 기반으로 구현된 함수이다.

reduce 함수는 인자로 이터러블을 받기 때문에 이를 기반으로 구현된 함수인 queryStr과 join도 기본적으로 이터러블 프로토콜을 따르고 있다.

이 말은 즉 이 둘도 평가를 지연할 수 있다는 뜻이며 따라서 구현 속 사용된 map, entries를 지연 평가를 하는 L.map과 L.entries로 대체할 수 있다.

 

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

const queryStr = pipe(
    L.entries,
    L.map(([k, v]) => `${k}=${v}`),
    join('&')
);
console.log(queryStr({ limit: 10, offset: 10, type: 'notice' }));

// output
// limit=10&offset=10&type=notice

 

 

++

for...in

객체에서 문자열로 지정된 key들을 순회한다.

for...of가 이터러블인 값을 순회하는 것이라면 for...in은 non-Symbol 속성에 대해서만 반복한다. 

 

for (variable in object) { ... }
  • variable : object 안의 key들이 이곳에 들어간다.
  • object : 반복 작업을 수행할 객체로 열거형 속성을 가지고 있다.

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/for...in

 

for...in - JavaScript | MDN

for...in문은 상속된 열거 가능한 속성들을 포함하여 객체에서 문자열로 키가 지정된 모든 열거 가능한 속성에 대해 반복합니다. (Symbol로 키가 지정된 속성은 무시합니다.)

developer.mozilla.org

 

 

 

 

 

댓글