Daily Front_Minhhk

[JS] 일급객체, 고차함수(내장)[map,filter,reduce()] 본문

Code개발일지

[JS] 일급객체, 고차함수(내장)[map,filter,reduce()]

Minhhk 2022. 11. 18. 01:57

학습 목표

  • 일급 객체(first-class citizen)의 세 가지 특징을 설명할 수 있다.
  • 고차 함수(higher-order function)에 대해 설명할 수 있다.
  • 고차 함수를 자바스크립트로 작성할 수 있다.

일급 객체(1급객체, First Class Object)

일급객체에 대한 정의

일급객체(First-class Object)란 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 가리킨다. [위키백과]

JavaScript에도 특별한 대우를 받는 일급 객체(first-class citizen)가 있습니다.

대표적인 일급 객체 중 하나가 함수입니다.

일급객체의 조건에 대해서 정의

변수에 할당(assignment)할 수 있다.

다른 함수를 인자(argument)로 전달 받는다.

다른 함수의 결과로서 리턴될 수 있다. → 함수를 리턴하는 함수를 커링함수라고도 한다

문자열, 숫자 같은 다른 데이터처럼 사용 가능!

위에 대한 조건으로 인해 알 수 있는 것은 함수를 데이터(string, number, boolean, array, object) 다루 듯이 다룰 수 있다는 점이다.

여기서 데이터를 다룬다는 의미는 변수에 할당이 가능하다는 것으로, 함수 역시 할당이 가능하다는 의미이다.

그렇다면, 함수가 일급객체이기 때문에 할 수 있는 것은 무엇인가?

고차함수(Higher order function)를 만들 수 있다.

콜백(callback)을 사용할 수 있다.

  1. 변수에 함수를 할당하는 경우
/*
 * 아래는 변수 square에 함수를 할당하는 함수 표현식입니다.
 * JavaScript에서 함수는 일급 객체이기 때문에 변수에 할당할 수 있습니다.
 *
 * 함수 표현식은 할당 전에 사용할 수 없습니다.
 * square(7); // --> ReferenceError: Can't find variable: square
 */

const square = function (num) {
  return num * num;
};

// 변수 square에는 함수가 할당되어 있으므로 (일급 객체), 함수 호출 연산자 '()'를 사용할 수 있습니다.
output = square(7);
console.log(output); // --> 49
  1. 다른 함수를 인자(argument)로 받는다.
function mul(num) {
  return num*num;
}

// func는 매개변수임, 이름은 아무거나 지정해도 상관없음
function mulNum(func, number) {
  return func(number);
}

let result = mulNum(mul, 3); // 9

고차 함수

학습 목표

  • 배열 내장 고차 함수 filter에 대해서 이해할 수 있다.
  • filter에 대한 이해를 기반으로, 나머지 내장 고차 함수를 학습할 수 있다.
    • filter, map, reduce, forEach, find, sort, some, every
  • 고차 함수를 쓰는 이유를 설명할 수 있다 → 추상화를 통한 효율성 증대를 위하여!!!

고차 함수의 이해

고차 함수(higher order function)는 함수를 전달인자(argument)로 받을 수 있고, 함수를 리턴할 수 있는 함수입니다.

함수는 변수에 저장할 수 있습니다. 그리고 함수는, 함수를 담은 변수를 전달인자로 받을 수 있습니다.

마찬가지로, 함수 내부에서 변수에 함수를 할당할 수 있습니다. 그리고 함수는 이 변수를 리턴할 수 있습니다.

여기서 변수에 할당하지 않고 함수를 바로 이용할 수 있습니다.

어떤 고차 함수에 함수를 전달인자로 전달하고, 고차 함수는 함수 자체를 리턴합니다.

변수가 빠졌을 뿐, 동일하게 동작합니다.

이때 다른 함수(caller)의 전달인자(argument)로 전달되는 함수를 콜백 함수(callback function)라고 합니다.

[

프로그래밍에서 콜백(callback) 또는 콜백 함수(callback function)는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다. 콜백을 넘겨받는 코드는 이 콜백을 필요에 따라 즉시 실행할 수도 있고, 아니면 나중에 실행할 수도 있다.

]

어떤 작업이 완료되었을 때 호출하는 경우가 많아서, 답신 전화를 뜻하는 콜백 함수라는 이름이 붙여졌습니다.

콜백 함수를 전달받은 고차 함수(caller)는, 함수 내부에서 이 콜백 함수를 호출(invoke)할 수 있고, 조건에 따라 콜백 함수의 실행 여부를 결정할 수도 있습니다.

아예 호출하지 않을 수도 있고, 여러 번 실행할 수도 있습니다. 특정 작업의 완료 후에 호출하는 경우는 이후에 충분히 접할 수 있습니다.

'함수를 리턴하는 함수'는 모양새가 특이한 만큼, 부르는 용어가 따로 있습니다.

'함수를 리턴하는 함수'를 고안해 낸 논리학자 하스켈 커리(Haskell Curry)의 이름을 따,

커링 함수라고 합니다.

따로 커링 함수라는 용어를 사용하는 경우에는, 고차 함수라는 용어를 '함수를 전달인자로 받는 함수'에만 한정해 사용하기도 합니다.

그러나 정확하게 구분하자면, 고차 함수가 커링 함수를 포함합니다.

🫠🫠

📌 고차함수
- 함수를 전달인자로 받는 함수
- 함수를 리턴하는 함수

 

📌 콜백함수(callback)
- 전달인자(Argument)로 받는 함수이다. 

 


 

내장 고차 함수

라이브 수업 중 번뜩 하는 사진 이었다~ 빠른 캡쳐! 멋진 사진이다ㅋㅋ

filter

주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환합니다.

 

📌 arr.filter(callback(element[, index[, array]])[, thisArg])

 

const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];

const result = words.filter(word => word.length > 6);

console.log(result);
// expected output: Array ["exuberant", "destruction", "present"]

map

배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환합니다

📌 arr.map(callback(currentValue[, index[, array]])[, thisArg])

 

const array1 = [1, 4, 9, 16];

// pass a function to map
const map1 = array1.map(x => x * 2);

console.log(map1);
// expected output: Array [2, 8, 18, 32]

reduce

📌 arr.reduce(callback[, initialValue])

 

배열의 각 요소에 대해 주어진 리듀서(reducer) 함수를 실행하고, 하나의 결과값을 반환합니다.

🤲🏻 리듀서 함수는 네 개의 인자를 가집니다.

>>

누산기 (acc)현재 값 (cur)현재 인덱스 (idx)원본 배열 (src)

리듀서 함수의 반환 값은 누산기에 할당되고, 누산기는 순회 중 유지되므로 결국 최종 결과는 하나의 값이 됩니다

const array1 = [1, 2, 3, 4];

// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
  (previousValue, currentValue) => previousValue + currentValue,
  initialValue
);

console.log(sumWithInitial);
// expected output: 10

실제 filter 활용 예시

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);

실제 map 활용 예시

map 활용 시, 아래 과정을 꼭 기억하세요.

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

문제

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

수도 코드

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

실제 코드

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

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

// 만화책 한 권의 부제를 리턴하는 로직(함수)
const findSubtitle = function (cartoon) {
  return cartoon.subtitle;
};

// 각 책의 부제 모음
const subtitles = cartoons.map(findSubtitle); // ['어머니의 쌀', ...]

 

 

.map 사용 하고 나면 길이가 같다ㅇㅇㅇ

.filter 는 길이가 다르다ㅇㅇㅇ


실제 reduce 활용 예시

reduce 활용 시, 아래 과정을 꼭 기억하세요.

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

문제

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

수도코드

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

실제 코드

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, '');
// 'Tim, Satya, Sundar, '

배열을 객체로

수도 코드

  • 배열의 각 요소 : 유저 정보
  • 응축하는 방법 (함수) : 유저 한 명의 이름 중 첫 글자를 주소록 객체 속성의 키(key)로, 유저의 정보를 주소록 객체 속성의 값(value)으로 추가합니다.
  • 원하는 형태 : 주소록 객체에 누적합니다.
  • 응축된 결과 : 모든 유저의 정보가 알파벳으로 구분된 주소록
function makeAddressBook(addressBook, user) {
  let firstLetter = user.name[0];

  if(firstLetter in addressBook) {
    addressBook[firstLetter].push(user);
  } else {
    addressBook[firstLetter] = [];
    addressBook[firstLetter].push(user);
  }

  return addressBook;
}

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

users.reduce(makeAddressBook, {});

// return value
{
  T: [
    { name: 'Tim', age: 40 }
  ],
  S: [
    { name: 'Satya', age: 30 },
    { name: 'Sundar', age: 50 }
  ]
}

고차함수의 중요성

학습 목표

  • 추상화에 대해 설명할 수 있다.
  • 추상화의 관점에서 고차 함수가 갖는 이점에 대해 설명할 수 있다.
  • 고차 함수를 통해 사고 수준에서의 추상화를 달성할 수 있다.

함수를 통해 얻은 추상화를, 한 단계 더 높인 것이 고차 함수입니다.

함수는 값(배열)을 전달받아, 이 값을 가지고 복잡한 작업을 수행합니다.

이는 값 수준에서의 추상화입니다.

  • 함수 = 값을 전달받아 값을 리턴한다 = 값에 대한 복잡한 로직은 감추어져 있다 = 값 수준에서의 추상화

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

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

다시 말해 고차 함수를 통해, 보다 높은 수준(higher order)에서 생각할 수 있습니다.

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

추상화의 수준이 높아지는 만큼, 생산성도 비약적으로 상승합니다.

function getOnlyMales(data) {
  return data.filter(function (d) {
    return d.gender === 'male';
  });
}

function getOnlyAges(data) {
  return data.map(function (d) {
    return d.age;
  });
}

function getAverage(data) {
  const sum = data.reduce(function (acc, cur) {
    return acc + cur;
  }, 0);
  return sum / data.length;
}

function compose(...funcArgs) {
  // compose는 여러 개의 함수를 인자로 전달받아 함수를 리턴하는 고차 함수입니다.
  // compose가 리턴하는 함수(익명 함수)는 임의의 타입의 data를 입력받아,
  return function (data) {
    // funcArgs의 요소인 함수들을 차례대로 적용(apply)시킨 결과를 리턴합니다.
    let result = data;
    for (let i = 0; i < funcArgs.length; i++) {
      result = funcArgs[i](result);
    }
    return result;
  };
}

// compose를 통해 함수들이 순서대로 적용된다는 것이 직관적으로 드러납니다.
// 각각의 함수는 다른 목적을 위해 재사용(reuse) 될 수 있습니다.
const getAverageAgeOfMale = compose(
  getOnlyMales, // 배열을 입력받아 배열을 리턴하는 함수
  getOnlyAges, // 배열을 입력받아 배열을 리턴하는 함수
  getAverage // 배열을 입력받아 `number` 타입을 리턴하는 함수
);

const result = getAverageAgeOfMale(data);
console.log(result); // --> 26

'Code개발일지' 카테고리의 다른 글

[OOP] 객체지향 프로그래밍  (0) 2022.11.18
[JS] 클래스와 인스턴스(ES5,ES6)  (0) 2022.11.18
Section1 회고  (0) 2022.11.16
[HTML_CSS_JS] 나만의 아고라 스테이츠 만들기👏🏻  (0) 2022.11.15
[GIT] Git협업하기  (0) 2022.11.15