일급 객체와 고차 함수의 등장

조 은길·2021년 12월 13일
0

Javascript 정리

목록 보기
15/48
post-thumbnail
post-custom-banner

Achievement Goals

  • 일급 객체(first-class citizen)의 세 가지 특징을 설명할 수 있다.
  • 고차 함수(higher-order function)에 대해 설명할 수 있다.
  • 고차 함수를 자바스크립트로 작성할 수 있다.
  • 배열 내장 고차함수 filter에 대해서 이해할 수 있다.
  • filter에 대한 이해를 기반으로, 나머지 고차함수를 스스로 학습할 수 있다.
    • forEach, find, filter, map, reduce, sort, some, every
  • 추상화(abstraction)에 대해 설명할 수 있다.
  • 추상화의 관점에서 고차 함수가 갖는 이점에 대해 설명할 수 있다.
  • 고차 함수를 활용하여 프로그램을 작성할 수 있다.

오늘 TIL은 " 코드스테이츠 "에서 학습한 내용과 개인적으로 보충 학습한 내용들을 바탕으로 작성됐다.

일급 객체의 3가지 특징은 다음과 같다.

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

그러나, " 모던 자바스크립트 Deep Dive "에서는 여기에 한 가지 특징을 더 추가한다.

  • 무명의 리터럴로 생성할 수 있다. 즉, 런타임(할당 단계)에 생성이 가능하다.

ex)

// 할당 단계에 함수 리터럴이 평가되어 함수 객체가 생성되고 변수에 할당된다.
let increase = function (num){
  return num++;
}

let decrease = function (num) {
  return num--;
}

정리해보면, JS 함수는 1급 객체이다.

그리고 함수가 1급 객체라는 사실이 주는 가장 큰 장점은 바로 고차 함수(high order function)가 가능하다는 점이다. 즉, 목적에 따라 적절한 함수를 인자로 넘겨줌으로써 JavaScript의 forEach, filter, map, reduce, sort 등의 배열 메소드 같은 편리한 고차함수들이 가능하다.

추상화(abstraction)

컴퓨터 공학의 근간을 이루는 여러 개념 중에 추상화(abstraction)가 있다. 추상화의 다른 말은 요약이다. 복잡한 어떤 것을 압축해서 핵심만 추출한 상태로 만드는 것이 추상화이다.

자바스크립트(를 비롯한 많은 프로그래밍 언어) 역시 추상화의 결과이다. 컴퓨터를 구성하는 장치(CPU)는 0과 1만 이해한다. 크롬 개발자 도구의 콘솔(console)탭에서 아래 코드를 입력했을 때, 어떤 과정을 거쳐 10이 출력되는지 몰라도 10을 출력할 수 있다. 그런 복잡한 것들은 크롬의 자바스크립트 해석기(엔진)가 대신 해주기 때문이다.

컴퓨터의 내부 구조에 대한 고민이 해결되었기 때문에, 자바스크립의 문법(syntax)을 올바르게 사용하는 것만으로 다양한 프로그램을 (자바스크립트가 없었을 때) 보다 쉽게 작성할 수 있다. 이처럼 고민거리가 줄어들고, 그래서 문제의 해결이 더 쉬워지는 것이 추상화의 이점이다.

추상화 = 생산성(productivity)의 향상

한편 프로그램을 작성할 때 자주 반복되어 사용되는 로직은 별도의 함수로 작성하기도 한다. 이 역시 추상화의 좋은 사례이다. 추상화의 관점에서 함수를 바라보면, 함수는 사고(thought) 또는 논리(logic)의 묶음이다.

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

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

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

이렇게 고차 함수는 추상화의 수준이 높아진만큼 생산성도 비약적으로 상승한다. 이를 예시를 통해 살펴 보자.

Example of using HoFs

사고 수준의 추상화의 예시

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

위에 제시된 getAverageAgeOfMaleAtOnce 함수는 배열 메소드를 적절하게 사용하여 순차적으로 원하는 작업을 수행한다. 꽤 괜찮은 코드이지만, '남성'의 '평균 나이'만 구하는 작업에만 국한된 함수이다. 개선점을 찾아보자면, 'male'을 매개변수화(parameterization)하여 조금 더 일반적인(generic) 함수로 변경할 수도 있다. 그래도 어디까지나 '남성' 또는 '여성'의 '평균 나이'를 구하는 작업에 그친다.

한편, filter, map, reduce 등의 배열 메소드는 다른 목적을 위해서 사용될 수도 있다. 예를 들어 '남성' 중 '최연소 나이'를 구하거나, '여성' 중 '최연소 나이와 최연장 나이의 차이'를 구할 때 이미 작성된 로직을 그대로 쓸 수 있다.

고차 함수를 통해 이를 쉽게 달성할 수 있습니다. 아래의 compose 함수는 입력받은 함수들을 순차적으로 결합하는 고차 함수이다. 각각의 작업(filter, map, reduce)들은 별도의 함수로 분리되어, compose의 인자로 전달되는 콜백 함수가 된다.

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

이처럼 고차 함수를 통해 사고 수준에서의 추상화를 달성할 수 있다. 즉, 생산성이 높아진다. 각각의 작업들은 다른 목적을 위해 재사용될 수 있다.


  • 고차 함수를 자바스크립트로 작성할 수 있다.
  • 배열 내장 고차함수 filter에 대해서 이해할 수 있다.
  • filter에 대한 이해를 기반으로, 나머지 고차함수를 스스로 학습할 수 있다.
profile
좋은 길로만 가는 "조은길"입니다😁
post-custom-banner

0개의 댓글