공부하는 블로그

Ramda.js를 이용한 함수형 프로그래밍 수련 본문

자바스크립트

Ramda.js를 이용한 함수형 프로그래밍 수련

devtimothy 2020. 2. 9. 21:51

Ramda.js를 이용한 함수형 프로그래밍 수련

예전에 면접보러 다니면서 했던 개발 테스트 중 함수형 프로그래밍으로 풀어낼 수 있는 과제가 있어 다시 풀어보고자 합니다. JSON 파일은 여기 를 참조하면 되겠습니다.

1. 각 유저별로 전체 이벤트 갯수를 구하기 - TotalEvent는 모든 이벤트 숫자를 더한 값

결과는 아래와 같이 나오면 됩니다.

[
  {
    login: "rafaelfranca",
    events: {
      TotalEvent: 19
    }
  },
  {
    login: "HeshamAbdalla",
    events: {
      TotalEvent: 10
    }
  }
]

현재 json 배열 내의 값은 아래의 형태를 지닙니다.

{
  "id": "8594176318",
  "type": "IssueCommentEvent",
  "actor": {
    "id": 6443532,
    "login": "bogdanvlviv"
  }
}

type 내에 있는 값이 이벤트인것 같습니다. login 이라는 키 값에는 actor.login 값이 들어가면 되겠네요.

저는 Ramda.js 를 익힐 겸 Ramda를 활용했습니다. 비슷한 종류로는 lodash, underscore 같은 녀석들이 있습니다.

const events = require("./event.json");
const R = require("ramda");
R.pipe(
  R.groupBy(item => item.actor.login),
  R.toPairs,
  R.map(([k, v]) => ({
    login: k,
    events: {
      TotalEvent: v.length
    }
  })),
  console.log
)(events);

선언된 순서대로 설명을 해보자면...

pipe

pipe는 함수형 언어의 주요 특징 중 하나인 함수 합성 을 하는 함수입니다. 비슷한 것으로는 R.compose 가 있는데, pipe는 왼쪽에서 오른쪽 순으로 합성을 한다면, compose는 오른쪽에서 왼쪽으로 합성을 합니다.

  • 저는 pipe를 선호하는데, 아무래도 우리가 글을 읽을때도 왼쪽에서 오른쪽으로 읽고, 위에서 아래로 읽기 때문에 코드 가독성도 높아져서 pipe를 더 선호합니다.

  • compose의 경우에는 최종 결과가 어떤 형태인지 먼저 보고 싶을 때 활용하면 좋을 듯 합니다.

    • Ramda.js의 compose 예제를 살펴봅시다.

    • const classyGreeting = (firstName, lastName) => "The name's " + lastName + ", " + firstName + " " + lastName
      const yellGreeting = R.compose(R.toUpper, classyGreeting);
      yellGreeting('James', 'Bond'); //=> "THE NAME'S BOND, JAMES BOND"
    • R.compose 선언된 부분을 살펴보면 R.toUpper 가 먼저 선언되어 있습니다. 이를 통해 "아 여긴 대문자가 나오겠구나"를 먼저 예상할 수 있습니다.

groupBy

groupBy를 이용해서 배열 내 객체의 이름별로 그룹화 할 수 있습니다. item => item.actor.login 이라는 선언을 통해서 오브젝트 내에 선언된 login 값을 이용해서 그룹화했습니다. 결과 값은 아래와 같습니다.

{ 
    bogdanvlviv:
   [ { id: '8594176318', type: 'IssueCommentEvent', actor: [Object] },
     { id: '8591675469', type: 'PullRequestEvent', actor: [Object] } ],
  mostrozny:
   [ { id: '8594070315', type: 'WatchEvent', actor: [Object] } ],
     ...
}

toPairs

Javascript 표준인 Object.entries 함수와 같은 기능입니다. 객체의 키, 값 쌍을 배열로 출력해 줍니다. toPairs 까지 합성한 결과는 아래와 같습니다.

[
    [ 'bogdanvlviv', [ [Object], [Object] ] ],
  [ 'mostrozny', [ [Object] ] ],
  ...
]

map

map을 통해서 이제 결과 도출을 해봅시다.

toPairs를 통해 얻어낸 key와 value의 길이를 이용해서 TotalEvents 값을 구할 수 있게 되었습니다.

[
    { login: 'bogdanvlviv',
    events: { TotalEvent: 2 } },
  { login: 'mostrozny', events: { TotalEvent: 1 } },
  ...
]

2. 각 유저별로 각 이벤트 갯수

결과는 아래 형태와 같게 나오면 됩니다. 1번 문제의 확장 버젼이네요. 각 이벤트 별 발생 횟수를 추가하면 되겠습니다.

[
  {
    login: "rafaelfranca",
    events: {
      TotalEvent: 19,
      IssueCommentEvent: 10,
      PullRequestEvent: 2,
      IssuesEvent: 2,
      PushEvent: 1,
      PullRequestReviewCommentEvent: 4
    }
    }
]

map 함수 내에서 R.countBy 를 이용하여 이벤트의 갯수를 쉽게 얻어냈습니다.

const events = require("./event.json");
const R = require("ramda");
R.pipe(
  R.groupBy(item => item.actor.login),
  R.toPairs,
  R.map(([k, v]) => {
    return {
      login: k,
      events: {
        TotalEvent: v.length,
        ...R.countBy(e => e.type)(v) // 이 부분이 추가되었습니다.
      }
    };
  }),
  console.log
)(events);

3. 정렬

  • 정렬 파라미터는 sort
  • 주어진 이벤트가 많은 유저 순으로 정렬
  • default값은 TotalEvent (전체 이벤트 수)

문제가 약간 애매하게 나왔는데, 전체 이벤트 수가 많은 순으로 정렬을 하되, 이벤트 종류가 많은 것부터 순서대로 정렬하라는 의미로 이해를 했습니다.

const events = require("./event.json");
const R = require("ramda");
R.pipe(
  R.groupBy(item => item.actor.login),
  R.toPairs,
  R.map(([k, v]) => {
    return {
      login: k,
      events: {
        TotalEvent: v.length,
        ...R.countBy(e => e.type)(v)
      }
    };
  }),
  // 이 이하 부분이 추가되었습니다.
  R.sortWith(
    [
      R.descend(
        R.pipe(R.prop("events"), R.prop("TotalEvent"))
      ),
      R.descend(
        R.pipe(R.prop("events"), R.keys, R.length)
      ),
    ]
  ),
  console.log
)(events);

sortWith

sortWith를 통해서 다양한 조건으로 정렬을 할 수 있습니다. R.descend(R.pipe(R.prop("events"), R.prop("TotalEvent"))) 의 경우에는 events.TotalEvent 의 값을 내림차순으로 정렬한다는 의미입니다.

R.descend(R.pipe(R.prop("events"), R.keys, R.length)) 의 경우에는 events의 종류 순으로 (keys.length) 내림차순 정렬을 하라는 의미입니다.

두 조건을 배열에 묶어서 sortWith로 보내어 배열의 정렬을 해줍니다.

결론

이렇게 함으로서 간략하게 Ramda.js를 이용하여 다양한 함수의 합성과 정렬을 통해서 원하는 값을 도출해보았습니다. for, while, if 문 등을 통해서 코딩을 했더라면 이렇게 깔끔하게 코드를 짤 수 있었을까? 하는 생각이 듭니다. 이 글을 읽으시는 분들도 이 글을 통해서 함수형 프로그래밍의 매력에 빠져보시는 것은 어떨까 제안해봅니다. 질문이 있으시다면 아래에 댓글을 달아주세요! 감사합니다 😃

'자바스크립트' 카테고리의 다른 글

근황: 리액트 학습  (2) 2021.08.14
First look of Svelte App  (0) 2020.02.03
FP 비교  (2) 2020.01.03
Vue.js Composition API 리뷰  (0) 2019.12.21
Vue.js의 새롭게 개선된 prop.sync  (0) 2019.05.10
Comments