JavaScript & TypeScript

[JS] 고차함수

반응형

이차방정식 마냥 이차함수라 하기는 이상하니까 고차함수라고 표현 한것 같습니다.

결국 함수안에 함수를 넣은 형태입니다. 스코프, 클로저와 비슷하게..

 

 

자바스크립트에 특별한 대우를 받는 일급 객체(first-class citizen)가 있습니다. 대표적인 일급 객체 중 하나가 함수입니다. (자바스크립트가 나온 시점을 고려했을 때,) 자바스크립트에서 함수는 아래와 같이 특별하게 취급됩니다.

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

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

 

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

 

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

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

 

'함수를 리턴하는 함수'는 모양새가 특이한 만큼, 부르는 용어가 따로 있습니다. '함수를 리턴하는 함수'를 고안해 낸 논리학자 하스켈 커리(Haskell Curry)의 이름을 따, 커리 함수라고 합니다. 따로 커리 함수라는 용어를 사용하는 경우에는, 고차 함수란 용어를 '함수를 인자로 받는 함수'에만 한정해 사용하기도 합니다. 그러나 정확하게 구분하자면, 고차 함수가 커리 함수를 포함합니다. '함수를 리턴하는 함수'와 '함수를 인자로 받는 함수' 모두, 용어를 고차 함수로 사용합니다.

 

function의 선언 만큼 return의 개수도 있습니다.

 

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

 

고차함수의 종류

map 

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

arr.map(function(변수인자){적용하고자 하는 코드})

고차함수는 위와 같은 구조를 가지고 있습니다.

filter

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

reduce

배열의 각 요소를 더해 주어진 함수를 실행하고, 하나의 결과값을 반환합니다.

 

네 개의 인자를 가집니다. 보통 acc, cur 까지만 선언을 주로 하게 됩니다.

  1. 누산기accumulator (acc)
  2. 현재 값 (cur)
  3. 현재 인덱스 (idx)
  4. 원본 배열 (src)

arr.reduce(function(accumulator, currentValue, currentIndex, array) {

return accumulator + currentValue;

});

콜백은 4번 호출됩니다. 각 호출의 인수와 반환값은 다음과 같습니다.

accumulator currentValue currentIndex array value
0 1 1 [0, 1, 2, 3, 4] 1
1 2 2 [0, 1, 2, 3, 4] 3
3 3 3 [0, 1, 2, 3, 4] 6
6 4 4 [0, 1, 2, 3, 4] 10

 

forEach

주어진 함수를 배열 요소 각각에 대해 실행합니다.

find

주어진 판별 함수를 만족하는 첫 번째 요소 을 반환합니다. 그런 요소가 없다면 undefined를 반환합니다.

sort

배열의 요소를 적절한 위치에 정렬한 후 그 배열을 반환합니다. 정렬은 stable sort가 아닐 수 있습니다. 기본 정렬 순서는 문자열의 유니코드 코드 포인트를 따릅니다.

some

배열 안의 어떤 요소라도 주어진 판별 함수를 통과하는지 테스트합니다. 빈 배열에서 호출은 false 반환

every

배열 안의 모든 요소가 주어진 판별 함수를 통과하는지 테스트합니다. 빈 배열에서 호출은 true 반환

 

 

 

고차함수를 써야하는 이유

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

 

추상화는 복잡한 어떤 것을 압축해서 핵심만 추출한 상태로 만드는 것이 추상화입니다. 우리가 살아가는 이 세상은, 추상화로 가득 차 있습니다. '-1'을 표현하는 현실의 방법은 존재하지 않습니다. 그러나 우리는 '-1'이라는 문자를 보고, -1은 0보다 1만큼 작은 수라고 설명할 수 있습니다. 이렇듯, 인간은 추상화를 통해 생각하고 표현합니다. 추상화를 이용하면, 효율적이고 편하게 생각할 수 있기 때문입니다.

 

브라우저 창에 주소를 입력했을 때, 어떤 일이 일어나는지 정확하게 알고 있나요? 입력한 내용을 전파하고, 어디 서버로 갔다가 다른 서버로 가는 등 그런 복잡한 내용을, 일상생활에서는 몰라도 됩니다. 우리는 그저 주소창에 올바른 주소를 입력하면, 브라우저가 해당 사이트를 보여 준다는 것만 알고 있습니다. 스마트폰으로 카카오톡이나 페이스북 메신저를 통해 친구에게 'ㅇㅇ'이란 메세지를 보내면, 그 순간 여러분들의 스마트폰은 기지국과 약 20개의 메세지를 주고받습니다. 하지만 우린 이런 것들을 전부 알지 못하고, 알 필요도 없습니다. 그러나 입력창에 메세지를 입력하고 전송 버튼을 누르면, 내 친구가 메세지를 받는다는 사실은 알고 있습니다.

 

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

 

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

getAverage 함수는 number 타입을 요소로 갖는 배열을 입력받아, 모든 요소의 평균값을 리턴합니다. 앞으로는 number 타입을 요소로 갖는 배열을 인자로 전달하기만 하면, 복잡한 로직은 신경 쓰지 않아도 평균값을 얻을 수 있습니다.

 

 

반응형

'JavaScript & TypeScript' 카테고리의 다른 글

[JS] 비동기, Node.js 모듈, fetch API  (0) 2021.06.24
[JS] 객체 지향  (1) 2021.06.14
[JS] DOM  (6) 2021.06.06
[JS] 복습(과제) 정리  (0) 2021.06.01
[JS] Spread, Rest, Destructing  (2) 2021.05.31