본문 바로가기

Backend/함수형 자바스크립트

기본 객체 다루기

이전 포스팅 참고

 

이전 포스팅에서 잠깐 본 것 처럼 Immutable.js 는 좋은 라이브러리입니다.

Immutable.js 를 결국 사용하는 이유는 중첩 구조의 값을 변경한 새로운 객체를 만들어 낼 수 있다는 것 때문이고, 이렇게 불변적으로 값을 다룸으로써 부수 효과를 줄일 수 있습니다. 근래엔 여러 단계 중첩 된 데이터를 많이 사용하므로 이러한 깊은 값의 변화는 꽤 중요한 포인트긴 합니다.

 

Immutable.js 의 단점이라 한다면 그 내부가 바로 JSON 이 될 수 없는 커스텀 객체라는 부분입니다.

비단 Immutable.js 만의 단점이라기보단, Sequelize 나 TypeORM 등의 Node.js 에서 사용되는 ORM 의 모델들에도 해당됩니다.

 

- 커스텀 객체

 

JSON 은 데이터를 주고 받는데 거의 빠질 수 없는 포맷입니다. JSON 으로 굉장히 풍성하게 데이터를 표현할 수 있으며 모든 환경에서 이를 파싱하고 있습니다. 위에서 말한 커스텀 객체로 데이터를 다루면 먼저 .toJSON() 을 해야 합니다. (Immutable.js 에선 toJS() 혹은 toJSON() 이 되겠네요) 커스텀 객체가 중첩되어 있다면 내부를 순회하면서 전부 .toJSON() 을 해야 하는데, 기본적으로 JSON 관련된 처리가 cpu 를 타는걸 생각하면 꽤 자원을 잡아먹는 일입니다. 커스텀 객체가 크면 클수록 cpu 도 많이 쓰고 처리 시간도 오래 걸리겠죠.

 

Immutable.js 를 사용한다면 결국 위의 단점들은 어느정도 안고 가야 하는데, 그러기보다는 바로 JSON 이 될 수 있는 자바스크립트 기본 객체를 쓰면서도 Immutable.js 의 기능을 대신 할 수 있는 함수들을 아래에서 알아보려고 합니다.

 

- _.sel

 

Partial.js 의 _.sel 함수를 보기 전에, 비슷한 기능을 하는 문자열로 된 path 를 통해 값을 조회하는 Lodash 예시를 보도록 하겠습니다.

 

const _ = require('lodash');

const users = [
  {
    id: 1,
    name: 'A',
    items: [
      { id: 1, name: 'Sword', price: 300 },
      { id: 2, name: 'Shield', price: 400 },
    ],
  },
  {
    id: 2,
    name: 'B',
    items: [
      { id: 2, name: 'Shield', price: 400 },
      { id: 3, name: 'Ax', price: 250 },
    ],
  }
];

console.info(_.get(users, '[0].name')); // 'A'
console.info(_.get(users, '[1].items[0].name')); // 'Shield'

 

위와 동일한 기능을 하는 함수를 Partial.js 에서 별도의 문법을 만들었는데, JSON selector 라고 하는 _.sel 함수입니다.

문자열로 작성이 기본이며, 함수적으로도 동작시킬 수 있습니다. 간단한 예시를 보겠습니다.

 

const _ = require('partial-js');

const users = [
  {
    id: 1,
    name: 'A',
    items: [
      { id: 1, name: 'Sword', price: 300 },
      { id: 2, name: 'Shield', price: 400 },
    ],
  },
  {
    id: 2,
    name: 'B',
    items: [
      { id: 2, name: 'Shield', price: 400 },
      { id: 3, name: 'Ax', price: 250 },
    ],
  }
];

console.info(_.sel(users, '0 -> name')); // 'A'
console.info(_.sel(users, '1 -> items -> 0 -> name')); // 'Shield'

 

사용법은 Lodash 와 거의 동일한데 어떻게 보면 좀 더 직관적인 것 같기도 하고, 쓸만해보입니다.

여기에 추가로 함수를 통한 복잡한 쿼리를 해보도록 하겠습니다.

 

const _ = require('partial-js');

const users = [
  {
    id: 1,
    name: 'A',
    items: [
      { id: 1, name: 'Sword', price: 300 },
      { id: 2, name: 'Shield', price: 400 },
    ],
  },
  {
    id: 2,
    name: 'B',
    items: [
      { id: 2, name: 'Shield', price: 400 },
      { id: 3, name: 'Ax', price: 250 },
    ],
  }
];

console.info(_.sel(users, '(u=>u.id===1) -> name')); // 'A'
console.info(_.sel(users, '(u=>u.id===2) -> items -> (i=>i.id===3) -> name')); // 'Ax'

 

이런 표현식도 나름 유용하게 쓸 수 있겠네요. _.sel 이 내부적으로 처리되는 로직을 표현하면 아래와 같습니다.

 

const _ = require('partial-js');

const users = [
  {
    id: 1,
    name: 'A',
    items: [
      { id: 1, name: 'Sword', price: 300 },
      { id: 2, name: 'Shield', price: 400 },
    ],
  },
  {
    id: 2,
    name: 'B',
    items: [
      { id: 2, name: 'Shield', price: 400 },
      { id: 3, name: 'Ax', price: 250 },
    ],
  }
];

_.go(users,
  _.find(_.l('u=>u.id===1')),
  user => user.name,
  console.info); // 'A'

 

위 예시에서 _.l 은 Partial.js 의 _.lambda 의 alias 입니다. _.sel 의 () 부분에 들어가는 부분은 _.find 의 predicate 와 동일해서 (val, idx, list) 인자를 모두 사용할 수 있습니다. 아래에선 위와 동일한 코드를 활용해 약간 TMI 인 문법 예시들을 보도록 하겠습니다.

 

_.go(users,
  _.find(_.l('$.id===1')),
  user => user.name,
  console.info); // 'A'
  
_.go(users,
  _.find(_.l('#1')),
  user => user.name,
  console.info); // 'A'

 

첫번째는 _.l 에선 인자를 하나만 사용할 때 기본 인자 이름을 $ 로 제공하는데, 이를 활용해 인자 부분 정의를 생략한 예시입니다.

두번째는 id 라는 키는 자주 사용되기 때문에 $.id 를 # 으로 대체할 수 있는 문법입니다.

 

Partial.js 에선 이런 부분도 제공을 하고 있어서 예시로 가져와봤지만, 제 생각엔 그리 좋은 것 같진 않습니다.

과도한 문법 생략은 오히려 코드 가독성을 해친다고 생각하며, 생략하지 않고 명시하는게 위의 예시들에선 더 좋지 않을까 싶습니다.

물론 이건 사견이므로 익숙해지면 더 편할 수 도 있으니, 개발자 취향에 따라가면 될 것 같습니다.

 

- 값 변경

 

Immutable.js 를 대신하려면 _.sel 에선 깊은 값 변경 또한 동일하게 지원해야 합니다.

 

const _ = require('partial-js');

const users = [
  {
    id: 1,
    name: 'A',
    items: [
      { id: 1, name: 'Sword', price: 300 },
      { id: 2, name: 'Shield', price: 400 },
    ],
  },
  {
    id: 2,
    name: 'B',
    items: [
      { id: 2, name: 'Shield', price: 400 },
      { id: 3, name: 'Ax', price: 250 },
    ],
  }
];

console.info(users[0].items[1]); // { id: 2, name: 'Shield', price: 400 }

_.set(users, '0 -> items -> (i=>i.id===2) -> price', 600);

console.info(users[0].items[1]); // { id: 2, name: 'Shield', price: 600 }

const test = _.im.set(users, '0 -> items -> (i=>i.id===2) -> price', 800);

console.info(users[0].items[1]); // { id: 2, name: 'Shield', price: 600 }
console.info(test[0].items[1]); // { id: 2, name: 'Shield', price: 800 }

 

_.set 은 객체 내부의 값을 직접 변경하는 함수고, _.im.set 은 _.set 의 immutable 버전입니다.

코드만 봐도 함수들에 의해 객체가 어떻게 처리되었는지 충분히 이해가 될 것입니다.

 

_.set 외에도 _.unset, _.pop, _.shift, _.push, _.unshift 와 같은 함수들로 깊은 값을 변경할 수 있습니다.

또한 _.im.set 처럼 각 함수 앞에 _.im 을 붙이면 immutable 버전으로 동작해 객체의 복사본을 리턴합니다.

 

함수형 프로그래밍에 대해 얘기하면서 Partial.js 를 중점적으로 봐왔기 때문에 Partial.js 의 함수들을 이용한 객체 컨트롤에 대해 좀 더 얘기하게 되었고, 꼭 이 라이브러리를 써야한다 는 주제는 아닙니다. Partial.js 의 함수들을 이용하면 기본 객체와 함수만으로 불변 값을 다룰 수 있기에 Immutable.js 와 비교해서 라이브러리를 선택 사용하면 되겠습니다.

 

- _.deep_pluck

 

간단한 함수 하나만 더 소개하려 합니다.

 

const _ = require('partial-js');

const users = [
  {
    id: 1,
    name: 'A',
    items: [
      { id: 1, name: 'Sword', price: 300 },
      { id: 2, name: 'Shield', price: 400 },
    ],
  },
  {
    id: 2,
    name: 'B',
    items: [
      { id: 2, name: 'Shield', price: 400 },
      { id: 3, name: 'Ax', price: 250 },
    ],
  }
];

console.info(_.deep_pluck(users, 'items.name')); // ['Sword', 'Shield', 'Shield', 'Ax']

 

깊은 곳에 있는 특정 property 의 내용을 한번에 조회하고 싶다면 _.deep_pluck 이라는 함수가 유용합니다.

 

Partial.js 의 함수들을 사용해 자바스크립트 기본 객체를 다뤄봤습니다.

만약 커스텀 객체였다면 메서드를 통해 값에 접근하거나 다뤄야 하므로 재사용성이 높은 함수를 만들기 어려울 수 있습니다.

또한 대체로 래핑되어있다보니 진짜 값은 숨어 있어서 다루기가 더 어려운 점도 있고요.

 

커스텀 객체만의 장점도 있겠지만 위에서 소개한 방법들을 사용해 중복 코드를 줄이고 기본 객체만을 사용해 cpu, 메모리, JSON 관련 추가 작업등의 수고로움을 줄일 수 있지 않을까 싶습니다.

'Backend > 함수형 자바스크립트' 카테고리의 다른 글

더 많은 함수형 자바스크립트 사용.. (2)  (0) 2021.12.04
더 많은 함수형 자바스크립트 사용.. (1)  (0) 2021.11.28
값 다루기  (0) 2021.11.06
지연 평가 (3)  (0) 2021.10.30
지연 평가 (2)  (0) 2021.10.23