고차함수

wanni kim·2021년 5월 1일
0

자바스크립트에서 일급 객체 중 하나인 함수에 대해서 공부해보자.

일급 객체 ( 함 수 )

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

함수를 변수에 할당할 수 있기 때문에, 함수를 배열의 요소나 객체의 속성값으로 저장할 수 있습니다. 이는 함수를 데이터(string, number, boolean, array, object)를 다루듯이 다룰 수 있다는 걸 의미합니다.

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

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

// square에는 함수가 저장되어 있으므로 (일급 객체), 함수 호출 연산자 '()'를 사용할 수 있습니다.
output = square(7);
console.log(output); // --> 49

함수 표현식은 함수를 변수에 저장할 수 있다.
함수 선언식은 호이스팅이 가능하다.

  • 호이스팅은 선언된 위치에 관계없이 어디서든 함수를 사용할 수 있도록 한다.
  • 코드가 실행되는 과정에서 함수 선언부를 코드의 최상단으로 끌어올리는 것처럼 보인다.

하지만 선언식이 호이스팅에 의존하면, 코드의 유지 보수가 어려워진다. 코드 리뷰나 디버깅을 할 때, 코드의 위 아래로 수없이 반복해야 할 수 있다. 반면에 함수 표현식은 함수의 할당과 실행의 위치에 따라 결과가 달라지기 때문에, 코드의 위치를 어느 정도 예측할 수 있다. 호이스팅의 차이점 빼고는 표현식과 선언식은 큰 차이가 없다. 다만, 함수 표현식의 경우에는 함수가 변수에 저장될 수 있다는 것이다.

그리고 함수는 변수에 저장된 데이터를 인자로 받거나, 리턴 값으로 사용할 수 있다. 함수도 변수에 저장될 수 있기 때문에 함수를 인자로 받거나, 리턴 값으로 사용할 수 있다.

고차 함수 (higher-order function)

고차 함수(higher order function)는 함수를 인자(argument)로 받을 수 있고, 함수의 형태로 리턴할 수 있는 함수. 함수는, 함수를 담은 변수를 인자로 전달받을 수 있다. 마찬가지로, 함수 내부에서 변수에 함수를 할당할 수 있다. 그리고 함수는 이 변수를 리턴할 수 있다. 여기서 변수에 할당하지 않고 함수를 바로 리턴할 수 있다. 어떤 고차 함수에 함수를 인자로 전달하고, 고차 함수는 함수 자체를 리턴한다. 변수가 빠졌을 뿐, 동일하게 동작한다.

이때 다른 함수(caller)의 인자(argument)로 전달되는 함수를 콜백 함수(callback function)라고 한다. 콜백 함수의 이름은, 어떤 작업이 완료되었을 때 호출하는 경우가 많아서, 답신 전화를 뜻하는 콜백이라 한다.

콜백 함수를 전달받은 고차 함수는, 함수 내부에서 이 콜백 함수를 호출(invoke) 할 수 있다. caller는 조건에 따라 콜백 함수의 실행 여부를 결정할 수 있다. 아예 호출하지 않을 수도 있고, 여러 번 실행할 수도 있다. 특정 작업의 완료 후에 호출하는 경우도 많다.

'함수를 리턴하는 함수'를 고안해 낸 논리학자 하스켈 커리(Haskell Curry)의 이름을 따, 커리 함수라고 한다. 따로 커리 함수라는 용어를 사용하는 경우에는, 고차 함수란 용어를 '함수를 인자로 받는 함수'에만 한정해 사용하기도 하지만. 정확하게 구분하자면, 고차 함수가 커리 함수를 포함하는것이다.

  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
  1. 함수를 리턴하는 경우
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
  1. 함수를 인자로 받고, 함수를 리턴하는 경우
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

내장 고차 함수

그 중 배열 메소드들을 알아보자.

  • filter

    • 걸러내기 위한 조건이 filter의 인자로 들어간다. 이떄 전달되는 조건은 함수의 형태이다.

      Array.prototype.filter = function(func) {
        const arr = this;
        const newArr = []
        for(let i = 0; i < arr.length; i++) {
          if (func(arr[i]) === true) {
            newArr.push(this[i])
          }
        }
       return newArr;
       
      이것을 활용하여 함수를 하나 작성해보면,
      
      // 함수 표현식
      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']
      
  • map

    • map은 배열의 모든 value를 map 메소드의 인자로 받은 함수를 통과하고 난 뒤의 새로운 값으로 만들어진 새로운 배열을 리턴한다.
    • 배열의 각 요소가 -> 함수에 의해 -> 다른 요소로 map 된다.
      var kvArray = [{key:1, value:10},
                     {key:2, value:20},
                     {key:3, value: 30}];
      var reformattedArray = kvArray.map(function(obj){
      	var rObj = {};
      	rObj[obj.key] = obj.value;
      	return rObj;
      	});
      // reformattedArray는 [{1:10}, {2:20}, {3:30}]
      // kvArray는 그대로
      // [{key:1, value:10},
      //  {key:2, value:20},
      //  {key:3, value: 30}]
      

reduce

  • reduce는 배열의 값들끼리의 계산을 실행시켜준다
    누적값을 가지고 현재값을 배열의 순서대로 입력받아 결국은 누적값에 최종 결과값을 저장하게 된다. 그리고 그 값을 리턴한다.
    예제를 보자.
var sum = [0, 1, 2, 3].reduce(function (accumulator, currentValue) {
  return accumulator + currentValue;
}, 0);

// sum is 6
화살표 함수로도 작성할 수 있다.
var total = [ 0, 1, 2, 3 ].reduce(
  ( accumulator, currentValue ) => accumulator + currentValue,
  0
);

  중첩된 배열을 펼칠수도 있다.

  var flattened = [[0, 1], [2, 3], [4, 5]].reduce(
    function(accumulator, currentValue) {
      return accumulator.concat(currentValue);
     },[]);

  // 펼친 결과: [0, 1, 2, 3, 4, 5]

객체 내의 값 인스턴스 개수 세기 (reduce)

var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];

var countedNames = names.reduce(function (allNames, name) {
  if (name in allNames) {
    allNames[name]++;
  }
  else {
    allNames[name] = 1;
  }
  return allNames;
}, {});
// countedNames is:
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }

속성으로 객체 분류하기

var people = [
  { name: 'Alice', age: 21 },
  { name: 'Max', age: 20 },
  { name: 'Jane', age: 20 }
];

function groupBy(objectArray, property) {
  return objectArray.reduce(function (acc, obj) {
    var key = obj[property];
    if (!acc[key]) {
      acc[key] = [];
    }
    acc[key].push(obj);
    return acc;
  }, {});
}

var groupedPeople = groupBy(people, 'age');
// groupedPeople is:
// {
//   20: [
//     { name: 'Max', age: 20 },
//     { name: 'Jane', age: 20 }
//   ],
//   21: [{ name: 'Alice', age: 21 }]
// }

배열의 중복 항목 제거

참고: Set과 Array.from()을 사용할 수 있는 환경이라면, let orderedArray = Array.from(new Set(myArray));를 사용해 중복 요소를 제거할 수도 있습니다.

let arr = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4];
let result = arr.sort().reduce((accumulator, current) => {
    const length = accumulator.length
    if (length === 0 || accumulator[length - 1] !== current) {
        accumulator.push(current);
    }
    return accumulator;
}, []);
console.log(result); //[1,2,3,4,5]
profile
Move for myself not for the others

0개의 댓글