21일차 - 고차 함수

dudu00·2022년 11월 17일
0

codestates

목록 보기
19/25

함수

대표적인 일급 객체 중 하나가 함수.
JavaScript에서 함수는 아래와 같이 특별하게 취급된다.

변수에 할당(assignment) 할 수 있다.
다른 함수의 전달인자(argument)로 전달될 수 있다.
다른 함수의 결과로서 리턴될 수 있다.

함수를 배열의 요소나 객체의 속성값으로 저장할 수 있다.
함수를 데이터(string, number, boolean, array, object)처럼 다룰 수 있다.

고차 함수

고차 함수(higher order function)는
함수를 전달인자(argument)로 받을 수 있고, 함수를 리턴할 수 있는 함수
함수를 전달인자로 받거나 함수를 리턴하는 함수를 고차함수
콜백함수는 함수를 리턴하는 함수가 아닌 함수의 전달인자로 전달되는 함수

1. 다른 함수를 인자로 받는 경우

function double(num) {
  return num * 2;
}

function doubleNum(func, num) {
  return func(num);
}

/*
 * 함수 doubleNum은 다른 함수를 인자로 받는 고차 함수입니다.
 * 함수 doubleNum의 첫 번째 인자 func에 함수가 들어올 경우
 * 함수 func는 함수 doubleNum의 콜백 함수입니다.
 * 아래와 같은 경우, 함수 double은 함수 doubleNum의 콜백 함수입니다.
 */
let output = doubleNum(double, 4);
console.log(output); // -> 8

2. 함수를 리턴하는 경우

function adder(added) {
  return function (num) {
    return num + added;
  };
}

/*
 * 함수 adder는 다른 함수를 리턴하는 고차 함수입니다.
 * adder는 인자 한 개를 입력받아서 함수(익명 함수)를 리턴합니다.
 * 리턴되는 익명 함수는 인자 한 개를 받아서 added와 더한 값을 리턴합니다.
 */

// adder(5)는 함수이므로 함수 호출 연산자 '()'를 사용할 수 있습니다.
let output = adder(5)(3); // -> 8
console.log(output); // -> 8

// adder가 리턴하는 함수를 변수에 저장할 수 있습니다.
// javascript에서 함수는 일급 객체이기 때문입니다.
const add3 = adder(3);
output = add3(2);
console.log(output); // -> 5

3. 함수를 인자로 받고, 함수를 리턴하는 경우

function double(num) {
  return num * 2;
}

function doubleAdder(added, func) {
  const doubled = func(added);
  return function (num) {
    return num + doubled;
  };
}

/*
 * 함수 doubleAdder는 고차 함수입니다.
 * 함수 doubleAdder의 인자 func는 함수 doubleAdder의 콜백 함수입니다.
 * 함수 double은 함수 doubleAdder의 콜백으로 전달되었습니다.
 */

// doubleAdder(5, double)는 함수이므로 함수 호출 기호 '()'를 사용할 수 있습니다.
doubleAdder(5, double)(3); // -> 13

// doubleAdder가 리턴하는 함수를 변수에 저장할 수 있습니다. (일급 객체)
const addTwice3 = doubleAdder(3, double);
addTwice3(2); // --> 8

내장함수

JavaScript에는 기본적으로 내장된 고차 함수가 여럿 있습니다.
그중에서 배열 메서드들 중 일부가 대표적인 고차 함수에 해당한다

1. 내장 고차 함수 filter

  • 배열의 각 요소가
  • 특정 논리(함수)에 따르면, 사실(true)일 때
  • 따로 분류합니다(filter).

문제
만화책 식객 27권의 정보가 배열에 담겨있습니다. 출판 연도가 2003년인 단행본만 담은 배열을 만드세요.

수도코드
배열의 각 요소 : 각 식객 1- 27권의 정보
특정 논리(함수) : 책의 출판 연도가 2003년입니다. (true / false)
따로 분류 : 출판 연도가 2003년인 책의 정보

filter는 이렇게 조건에 맞는 데이터만 분류(filtering) 할 때 사용

// 단행본 모음
const cartoons = [
  {
    id: 1,
    bookType: 'cartoon',
    title: '식객',
    subtitle: '어머니의 쌀',
    createdAt: '2003-09-09',
    genre: '요리',
    artist: '허영만',
    averageScore: 9.66,
  },
  {
    id: 2,
    // .. 이하 생략
  },
  // ... 이하 생략
]; 

// 단행본 한 권의 출판 연도가 2003인지 확인하는 함수
const isCreatedAt2003 = function (cartoon) {
  const fullYear = new Date(cartoon.createdAt).getFullYear()
  return fullYear === 2003;
}; 

// 출판 연도가 2003년인 책의 모음
const filteredCartoons = cartoons.filter(isCreatedAt2003);

배열의 filter 메서드는, 모든 배열의 요소 중에서 특정 조건을 만족하는 요소를 걸러내는 메서드
filter 메서드는, 걸러내기 위한 조건을 명시한 함수를 전달인자로 받기 때문에 고차 함수이다

filter 메서드는 배열의 요소를 콜백 함수에 다시 전달합니다. 콜백 함수는 전달받은 배열의 요소를 받아 함수를 실행하고, 콜백 함수 내부의 조건에 따라 참(true) 또는 거짓(false)을 리턴해야 합니다.

아래는 filter 메서드 사용 예시

// 함수 표현식
const isEven = function (num) {
  return num % 2 === 0;
};

let arr = [1, 2, 3, 4];
// let output = arr.filter(짝수);
// '짝수'를 판별하는 함수가 조건으로서 filter 메서드의 전달인자로 전달됩니다.
let output = arr.filter(isEven);
console.log(output); // ->> [2, 4]

const isLteFive = function (str) {
  // Lte = less then equal
  return str.length <= 5;
};

arr = ['hello', 'code', 'states', 'happy', 'hacking'];
// output = arr.filter(길이 5 이하)
// '길이 5 이하'를 판별하는 함수가 조건으로서 filter 메서드의 전달인자로 전달됩니다.
let output = arr.filter(isLteFive);
console.log(output); // ->> ['hello', 'code', 'happy']

2. 내장 고차 함수 map

수는 그대로!

  • 배열의 각 요소가
  • 특정 논리(함수)에 의해
  • 다른 요소로 지정(map) 됩니다.

문제
만화책 식객 27권의 정보가 배열에 담겨있습니다. 각 책의 부제(subtitle)만 담은 배열을 만드세요.

수도 코드
배열의 각 요소 : 각 식객 1- 27권의 정보
특정 논리(함수) : 책 한 권의 부제를 찾습니다.
다른 요소로 지정 : 각 식객 1- 27권의 부제

map은 이렇게 하나의 데이터를 다른 데이터로 매핑(mapping) 할 때 사용

3. 내장 고차 함수 reduce

  • 배열의 각 요소를
  • 특정 방법(함수)에 따라
  • 원하는 하나의 형태로
  • 응축합니다. (reduction)

문제
만화책 식객 27권의 정보가 배열에 담겨있습니다. 각 단행본의 평점의 평균을 리턴하세요.

수도코드
배열의 각 요소 : 각 식객 1- 27권의 정보
응축하는 방법 (함수) : 각 단행본의 평점을 누적값에 더합니다.
원하는 형태 : 숫자로 누적합니다.
응축된 결과 : 각 단행본의 평점의 합을 단행본의 길이로 나눈 평점의 평균

reduce는 이렇게 여러 데이터를, 하나의 데이터로 응축(reduce)할 때 사용
아래 예시처럼 배열의 reduce 메서드로 모든 책의 누적 평균을 구할 수 있다.

// 단행본 모음
const cartoons = [
  {
    id: 1,
    bookType: 'cartoon',
    title: '식객',
    subtitle: '어머니의 쌀',
    createdAt: '2003-09-09',
    genre: '요리',
    artist: '허영만',
    averageScore: 9.66,
  },
  {
    id: 2,
    // .. 이하 생략
  },
  // ... 이하 생략
];

// 단행본 한 권의 평점을 누적값에 더한다.
const scoreReducer = function (sum, cartoon) {
  return sum + cartoon.averageScore;
}; 

// 초기값에 0을 주고, 숫자의 형태로 평점을 누적한다.
let initialValue = 0 
// 모든 책의 평점을 누적한 평균을 구한다.
const cartoonsAvgScore = cartoons.reduce(scoreReducer, initialValue) / cartoons.length;

reduce의 색다른 사용법

배열을 문자열로

수도 코드

배열의 각 요소 : 유저 정보
응축하는 방법 (함수) : 하나의 유저의 이름과 쉼표를 이어 붙입니다(concat)
원하는 형태 : 문자열로 누적합니다.
응축된 결과 : 쉼표로 구분되는 모든 유저의 이름

function joinName(resultStr, user) {
  resultStr = resultStr + user.name + ', ';
  return resultStr;
}

let users = [
  { name: 'Tim', age: 40 },
  { name: 'Satya', age: 30 },
  { name: 'Sundar', age: 50 }
];

users.reduce(joinName, '');

ㅡ> 콜백 함수 joinName은 users 배열 안에 있는 요소의 이름을 하나로 응축
ㅡ> ''는 joinName에 resultStr로 들어간다.

고차함수

고차함수를 쓰는 이유

복잡한 어떤 것을 압축해서 핵심만 추출한 상태로 만드는 것이 추상화
추상화의 관점에서 함수를 바라보면, 함수는 사고(thought) 또는 논리(logic)의 묶음

예를들어 getAverage 함수는 배열을 인자로 받아, 평균값을 리턴

고차 함수는 이 추상화의 수준을 사고의 추상화 수준으로 끌어올린다.

값 수준의 추상화: 단순히 값(value)을 전달받아 처리하는 수준
사고의 추상화: 함수(사고의 묶음)를 전달받아 처리하는 수준

고차 함수 = 함수를 전달받거나 함수를 리턴한다 = 사고(함수)에 대한 복잡한 로직은 감추어져 있다 = 사고 수준에서의 추상

고차 함수와 추상화

남성들의 평균 나이를 구하는 하나의 함수 getAverageAgeOfMaleAtOnce
reduce로 합하는 느낌

const data = [
  {
    gender: 'male',
    age: 24,
  },
  {
    gender: 'male',
    age: 25,
  },
  {
    gender: 'female',
    age: 27,
  },
  {
    gender: 'female',
    age: 22,
  },
  {
    gender: 'male',
    age: 29,
  },
];
function getAverageAgeOfMaleAtOnce(data) {
  const onlyMales = data.filter(function (d) {
    // data.filter는 배열의 각 요소에 인자로 전달받은 함수를 적용하고,
    // 그 결과가 true인 요소만을 갖는 배열을 리턴합니다.
    return d.gender === 'male';
  });

  const numOfMales = onlyMales.length;

  const onlyMaleAges = onlyMales.map(function (d) {
    // onlyMales.map는 배열의 각 요소에 인자로 전달받은 함수를 적용하고,
    // 각 결과를 요소로 갖는 배열을 리턴합니다.
    return d.age;
  });

  const sumOfAges = onlyMaleAges.reduce(function (acc, cur) {
    // onlyMaleAges.reduce는 배열의 각 요소에 인자로 전달받은 함수를 적용하고,
    // 각 결과를 두 번째 인자로 전달받은 초기값(0)에 누적한 결과를 리턴합니다.
    return acc + cur;
  }, 0);

  return sumOfAges / numOfMales;
}
profile
성장일지

0개의 댓글