hoon's bLog

Javascript 자바스크립트 코딩의 기술 7장 유연한 함수를 만들어라 (2) 본문

IT/Javascript

Javascript 자바스크립트 코딩의 기술 7장 유연한 함수를 만들어라 (2)

개발한기발자 2024. 2. 23. 08:45
반응형


7장 유연한 함수를 만들어라

tip34. 부분 적용 함수로 단일 책임 매개변수를 관리하라

  • 고차 함수를 이용해 매개변수에 단일 책임을 부여하는 방법을 살펴보자.
  • 고차 함수(Higher-Order Function)
    • 함수를 파라미터로 전달받거나 연산의 결과로 반환해주는 메서드
    • 다른 함수를 반환하므로 최소 2단계의 매개변수가 존재
    • 자주 거론되는 함수형 프로그래밍의 핵심이기도 하며, 자바스크립트를 함수형 프로그래밍에 알맞은 언어로 만들어주는 특성
    • 함수형 프로그래밍 : 함수형 프로그래밍은 함수를 다른 함수의 파라미터로 넘길 수도 있고 반환(return) 값으로 함수를 받을 수도 있는 프로그래밍 형태
  • 웹사이트에 행사 안내 페이지가 있다고 가정하자. 아래 코드를 통해 살펴보자.
    • 장소, 건물(building), 관리자(manager) 등은 크게 달라지지 않지만, 행사 내용(program)이 달라질 수 있다.
    • 건물, 담당자, 프로그램 또는 전시회라는 세 가지 인수를 받아 하나의 정보 집합으로 결합하는 함수를 만들자.
const building = {
  hours: '8 a.m. - 8 p.m.',
  address: 'Jayhawk Blvd',
};

const manager = {
  name: 'Augusto',
  phone: '555-555-5555',
};

const program = {
  name: 'Presenting Research',
  room: '415',
  hours: '3 - 6',
};

const exhibit = {
  name: 'Emerging Scholarship',
  contact: 'Dyan',
};

function mergeProgramInformation(building, manager, event) {
  const { hours, address } = building;
  const { name, phone } = manager;
  const defaults = {
    hours,
    address,
    contact: name,
    phone,
  };

return { ...defaults, ...event };
}
  • 함수 mergeProgramInformation에서 첫번째, 두번째 매개변수는 (building, manager)로 항상 동일하고, 반복 호출 중이다.
  • 함수의 return 값을 받는 변수를 선언하여 함수를 호출하면 아래와 같다.
const programInfo = mergeProgramInformation(building, manager, program);
// {
//  hours: "3 - 6", 
//  address: "Jayhawk Blvd", 
//  contact: "Augusto", 
//  phone: "555-555-5555", 
//  name: "Presenting Research", …}

const exhibitInfo = mergeProgramInformation(building, manager, exhibit);
//  {
//     address: "Jayhawk Blvd"
//     contact: "Dyan"
//     hours: "8 a.m. - 8 p.m."
//     name: "Emerging Scholarship"
//     phone: "555-555-5555"
//  }
  • 고차함수를 이용해서 단일 책임 매개변수를 만들어 앞에 위치한 2개의 인수를 재사용하면, 아래와 같다.
function mergeProgramInformation(building, manager) {
  const { hours, address } = building;
  const { name, phone } = manager;
  const defaults = {
    hours,
    address,
    contact: name,
    phone,
  };

  return program => {
    return { ...defaults, ...program };
  };
}

const programInfo = mergeProgramInformation(building, manager)(program);
const exhibitInfo = mergeProgramInformation(building, manager)(exhibit);
  • 첫 번째 매개변수 조합은 building, manager 기본 동일한 데이터이며, 두 번째 매개변수는 기초 데이터를 덮어 쓰는 사용자의 지정 정보이다.
  • 고차함수 호출법은 괄호에 이어 괄호를 작성하면 된다.
  • 반복까지는 제거되지 않았지만 부분 적용을 사용하면 가능하다. 다음 팁에서 배운다. (반환된 함수를 재사용하는 방법)
  • 나머지 매개변수는 한번만 사용이 가능하지만, 때로는 반복 사용이 필요한 경우도 있다.
  • 배열 데이터가 있거나 원본 데이터에 일대일로 대응되는 추가 데이터가 있는 경우 자주 발생한다.
  • 아래는 지역과 새를 결과값 배열로 쌍으로 연결해야 하는 코드이다.
function getBirds(...states) {
  return ['meadowlark', 'robin', 'roadrunner'];
}

const birds = getBirds('kansas', 'wisconsin', 'new mexico');
// ['meadowlark', 'robin', 'roadrunner']

const zip = (...left) => (...right) => {
  return left.map((item, i) => [item, right[i]]);
};

zip('kansas', 'wisconsin', 'new mexico')(...birds);
// [
//   ['kansas', 'meadowlark'],
//   ['wisconsin', 'robin'],
//   ['new mexico', 'roadrunner']
// ]
  • 함수를 함수 zip은 2개의 배열을 쌍으로 결합하는 함수
  • 원본 배열을 넘겨 받는 고차함수가 필요(...left)
  • 결과값을 배열을 넘겨받아서 결합하는 함수를 반환(...right)
  • 변수가 독립적이므로 나머지 매개변수를 두 번 모두 사용 가능하다.

tip35. 커링과 배열 메서드를 조합한 부분 적용 함수를 사용하라

  • 다음은 함수의 부분 적용을 통해 변수를 저장해두는 방법을 살펴보자.
  • 앞에 팁에서 살펴봤던 예제코드를 이어 간다.
    • (building, manager) 를 재사용
    • 첫번째 함수 호출의 반환값을 변수에 할당하면 된다.
// 고차함수 이용
const setStrongHallProgram = mergeProgramInformation(building, manager);
const programInfo = setStrongHallProgram(program);
const exhibitInfo = setStrongHallProgram(exhibit);

// 하드 코딩
const setStrongHallProgram = program => {
  const defaults = {
    hours: '8 a.m. - 8 p.m.',
    address: 'Jayhawk Blvd',
    name: 'Augusto',
    phone: '555-555-5555'
  }
  return { ...defaults, ...program}
}
const programs = setStrongHallProgram(program);
const exhibit = setStrongHallProgram(exhibit);
  • 고차 함수를 이용하면 매개변수를 별도로 분리할 수 있다.
  • 하지만, 함수를 완전히 분리하기 전에 함수에 필요한 인수의 수를 줄일 수 있도록, 인수를 분리하는 것이 훨씬 더 중요하다
  • 커링(currying) : 한 번에 인수를 하나만 받는 함수
  • 커링은 인수 하나를 받는 고차함수가 다른 함수를 반환한다. 이때 반환되는 함수 역시 인수 하나만 받을 수 있다.
  • 부분 적용 함수는 원래의 함수보다 항수(인수의 수)가 적은 함수를 반환한다.
    (예를 들면, 3개의 인수를 받는 함수인데 2개의 인수를 받는 함수를 반환)
  • 아래 코드는, 강아지 배열과 필터 조건을 인수로 받은 후 필터링 조건에 맞는 강아지의 이름만 모아서 반환하는 함수.
const dogs = [
  {
    이름: '맥스',
    무게: 10,
    견종: '보스턴 테리어',
    지역: '위스콘신',
    색상: '검정색',
  },
  {
    이름: '도니',
    무게: 90,
    견종: '래브라도레트리버',
    지역: '캔자스',
    색상: '검정색',
  },
  {
    이름: '섀도',
    무게: 40,
    견종: '래브라도레트리버',
    지역: '위스콘신',
    색상: '갈색',
  },
];

function getDogNames(dogs, filter) {
  const [key, value] = filter;
  return dogs
    .filter(dog => dog[key] === value)
    .map(dog => dog['이름']);
}

getDogNames(dogs, ['색상', '검정색']);
// ['맥스', '도니']
  • 정상적으로 작동하는듯 하나, 2가지 문제 발생한다.
    • filter 함수의 제약(=== 를 사용해야 한다. 예를 들면, 무게 20kg 이하인 강아지를 찾기 불가능)
    • map은 검사하는 항목만 인수로 받고, 외부 변수는 사용할 수 없다.
  • 먼저, 첫번째 문제부터 해결하자. filter와 map 함수를 다른 방식으로 이용하여 기준 체중보다 적게 나가는 강아지를 찾는 함수
function getDogNames(dogs, filterFunc) {
  return dogs
  .filter(filterFunc)
  .map(dog => dog['이름'])
}

getDogNames(dogs, dog => dog['무게'] < 20);
// ['맥스']
  • line 3 : filter 함수에함수에 콜백 함수인 filterFunc를 전달한다.
  • 하지만 여전히 20과 같은 값을 하드코딩되어 있다. 즉, 20이 아닌 다른 변수를 사용할 때 직접 코딩해서 넣거나, 유효 범위의 충돌이 없는지 확인하는 절차를 거치고 있다.
  • 이번엔 아래 코드와 같이, 부분 적용 함수를 이용해보자.
const weightCheck = weight => dog => dog['무게'] < weight;
getDogNames(dogs, weightCheck(20));
// ['맥스']
getDogNames(dogs, weightCheck(50));
// ['맥스', '섀도']
  • getDogNames 함수를 다시 작성할 필요 없다.
  • 전달하는 값 자체가 필터링하는 기준이 되어, 어떤 무게에서든 재사용이 가능하고, 유효 범위 충돌이 발생할 가능성도 거의 없다.
  • 다음은 아래와 같이 커링을 사용해보자.
const identity = field => value => dog => dog[field] === value;
const colorCheck = identity('색상');
const stateCheck = identity('지역');

getDogNames(dogs, colorCheck('갈색'));
// ['섀도']
getDogNames(dogs, stateCheck('캔자스'));
// ['섀도']
  • 커링을 사용하면 두 개의 함수와, 두 개의 인수 집합을 제한할 필요 없다.
  • 이를 응용하여, 모든 조건을 충족하거나, 최소한 하나의 조건을 충족하는 강아지를 찾는 경우
function allFilters(dogs, ...checks) {
  return dogs
  .filter(dog => checks.every(check => check(dog)))
  .map(dog => dog['이름']);
}
allFilters(dogs, colorCheck('검정색'), stateCheck('캔자스'));
// ['도니']

function anyFilters(dogs, ...checks) {
  return dogs
  .filter(dog => checks.some(check => check(dog)))
  .map(dog => dog['이름']);
}

anyFilters(dogs, weightCheck(20), colorCheck('갈색'));

tip36. 화살표 함수로 문맥 혼동을 피하라

  • 화살표 함수를 이용해 문맥 오류를 피하는 방법과 객체에서 this가 메서드에서 어떻게 작동되는지 살펴보자.
const validator = {
  message: '는 유효하지 않습니다.',
  setInvalidMessage(field) {
    return `${field}${this.message}`;
  },
};

validator.setInvalidMessage('도시');
// 도시는 유효하지 않습니다.
  • this는 객체 validator를 가리킨다.
  • setInvalidMessage 메서드가 호출될 때, 함수에서 this 바인딩을 생성하면서 객체를 문맥에 포함시킨다.
  • 다음 코드는 객체에 담긴 함수를 다른 함수의 콜백 함수로 사용하는 경우이다.
const validator = {
  message: '는 유효하지 않습니다.',
  setInvalidMessages(...fields) {
    return fields.map(function (field) {
      return `${field} : ${this.message}`;
    });
  },
};
validator.setInvalidMessages('aa','bb')
// ['aa : undefined','bb : undefined']

 

  • 책에서는 에러 난다고 message 속성이 없다고 에러난다고 하지만, 실행시키면 위와 같이 undefined를 반환한다.
  • 객체 메서드의 콜백함수의 this는 전역을 바라본다.
  • map() 메서드 문맥에서 호출되므로, 바인딩이 validator 객체가 아니고 우리가 원하는 message 값을 가져오지 못한다.
const validator = {
  message: '는 유효하지 않습니다.',
  setInvalidMessages(...fields) {
    return fields.map(field => {
      return `${field}${this.message}`;
    });
  },
};

validator.setInvalidMessages('도시');
// ['도시는 유효하지 않습니다.']
  • line 4 : 화살표 함수를 사용하면 문맥을 새로 바인딩하지 않아 validator에 바인딩 된다.
    (해당 구간이 화살표 함수 영역인데, 화살표 함수는 this를 만들지 않는다.)
  • line 5 : this는 validator로 바인딩되어있어서 화살표함수에서 this는 validator로 바인딩된다.
  • 아래와 같은 경우도 주의해야 한다.
const validator = {
  message: '는 유효하지 않습니다.',
  setInvalidMessage: field => `${field} : ${this.message}`,
};
validator.setInvalidMessage('hi');
// "hi : undefined"
  • line 3 : 화살표 함수는 새로 문맥을 만들지 않기 때문에 전역을 참고한다!!!!!!
  • 때문에 validator 상위에서 this를 가져다 쓰므로 undefined 출력!!!

Reference

자바스크립트 코딩의 기술 요약 / 정리

728x90
반응형