킹의 개발일지

쏙쏙 들어오는 함수형 코딩 33일차 본문

독서/책너두 챌린지

쏙쏙 들어오는 함수형 코딩 33일차

k1ng 2023. 9. 14. 02:17

33일차 <중첩된 객체에 함수형 도구 사용하기>

어제는 중첩이 없는 객체에 대해서 함수형 도구 update를 사용해봤다. 오늘은 중첩된 객체에 함수형 도구를 사용해보자.

 

우선 다룰 객체를 보자. shirt 객체는 내부에 options라는 중첩 객체를 가지고 있다.

var shirt = {
 name: "shirt",
 price: 13,
 options: {
  color: "blue",
  size: 3
 }
}

 

이때 size를 변경해주고 싶을 때는 어떻게 해야할까? 간단히 생각해 볼 수 있는 코드를 먼저 보자.

function incrementSize(item) {
  var {size: {options}} = item;
  var newSize = size + 1;
  var newOptions = objectSet(options, "size", newSize);
  var newItem = objectSet(item, "options", newOptions);
  return newItem;
}

 

나는 이미 update 함수를 알고 있기에 위 코드를 보면 냄새나는 부분을 어서 고치고 싶은 생각이 든다! 고쳐보자.

fuction incrementSize(item) {
  return update(item, "options", (options) => {
    return update(options, "size", increment);
  });
}

첫번째 update 함수의 콜백으로 size를 변경하는 update 함수를 넣으면 된다! 하지만 이 함수도 냄새가 난다. 암묵적 인자가 많다. 우선 함수 이름에 increment, size 두 단어를 사용하고 있고 이를 중첩된 update 함수 파라미터로 넘기고 있다. 암묵적 인자를 명시적으로 바꿔줘 보자.

function updateOption(item, option, modify) {
  return update(item, "options", (options) => {
    return update(options, option, modify);
  })
}

아직 멀었다! 함수이름에 Option이라는 암묵적 인자가 또 남았다. ㅠㅠ 이를 또 없애보자

function updateDoubleNestedObj(object, firstOption, secondOption, modify) {
  return update(object, firstOption, (optionValue) => {
    return update(optionValue, secondOption, modify);
  })
}

이제 맨 앞에서 봤던 incrementSize를 위에서 만든 메서드를 사용해서 다시 만들어보자.

 

#원본

// 원본
function incrementSize(item) {
  var {size: {options}} = item;
  var newSize = size + 1;
  var newOptions = objectSet(options, "size", newSize);
  var newItem = objectSet(item, "options", newOptions);
  return newItem;
}


#리팩터링

// 리팩터링
function incrementSize(item) {
  return updateDoubleNestedObj(item, "options", (size) => size + 1);
}

리팩터링 후 훨씬 간결해진 모습을 볼 수 있다!


위에서 두번 중첩된 객체를 변경하는 함수를 만들어 보았다. 하지만 이걸로 충분하지 않다! 3번 1만번 중첩된 객체가 있다면!! (1만번은 좀 많이 갔지만..) 언제 이렇게 만들고 있을 것인가!(1만번이란 숫자는 생활코딩님께서 자주 사용하시던..)

 

updateDoubleNestObj를 보면 바꿀 필드에 도달하기 전까지는 단순히 조회하는 동작만 수행하는 것을 알 수 있다. 조회를 계속하다 바꿀 필드를 만나면 modify를 수행한다. 재귀의 냄새가 난다.

 

재귀를 사용해서 중첩된 객체를 변경하는 nestedUpdate

원하는 필드를 찾아가기까지(?) 지도가 필요하다. 예를들어서 cart라는 객체를 살펴보자.

var cart = {
  shirt: {
    name: "shirt",
    price: 13,
    options: {
      color: "blue",
      size: 3
    }
  }
}

size를 바꾸기 위해서는 cart -> shirt -> options -> size와 같은 중첩에 접근하는 경로가 필요하다.

이를 배열로 준다면 재귀를 사용해서 중첩된 객체를 변경하는 함수를 만들 수 있을 듯 하다.

 

이제 nestedUpdate를 만들어보자.

function nestedUpdate(object, keys, modify) {
  if(keys.length === 0) {  // 재귀 종료조건
    return modify(object);
  }
  
  var firstKey = keys[0];
  var restOfKeys = keys.slice(1);
  return update(object, firstKey, (firstValue) => {
    return nestedUpdate(firstValue, restOfKeys, modify);
  });
}

keys에 담긴 원소들을 객체의 키로 사용해서 따라가다가 마지막 키에 다달아서야 종료조건을 만나 객체를 변경하고 마무리한다.

 

끝으로 위에서 본 cart객체의 size 필드를 변경하는 함수를 다시 작성해보자.

function incrementSizeByName(cart, name) {	// 여기서 name은 shirt 같은 상품의 이름을 의미, 즉 첫번째 키
  return nestedUpdate(cart, [name, "options", "size"], (size) => size + 1);
}

와우, 훨씬 간결하지 않은가!!


알고리즘 공부를 하면서 재귀파트가 진짜 이해가 안 가고 힘들었다. 특히 하노이 탑 움직일때 내 뇌도 같이 움직이는듯 했다. 이를 함수형 코딩에서 다시 볼 줄이야... 근데 실제와 관련없는 하노이탑을 움직이다 실제 상황을 인지하고 개선하는 작업이 필요해 질때 이해가 엄청 빨리 됐다. 역시 사람은 상황에 빠져봐야 공감을 한다.

 

저자의 말로는 함수형 코딩은 재귀를 자주 만날 수 밖에 없다고 한다. 자주보면 원수도 친구가 된다던데, 재귀야! 친해져보자!!