본문 바로가기

Programming/TIL

Higher Order Function [고차함수] 1편

1. HOF (Higher Order Function)

 

오늘 알아볼 주제는 바로 HOF, 고차함수다.

고차함수란, 함수를 다루는 함수라고 말할 수 있다.

 

예를 들어서: 

 

  • 하나 이상의 함수를 인자로 받는다.
  • 함수를 결과로 반환한다.

HOF를 통해 우리는 더 유연하고 기능적인 코드를 사용할 수 있다.

 

고차함수의 간단한 예제를 먼저 살펴보자.

const twice = function(f, v) {
  return f(f(v));
 }; 
 
 const plusOne = function(v) {
   return v + 1;
 };
 
 console.log(twice(plusOne, 1)); // 3

위 예제에서 twice()는 HOF로 사용되는데, 함수를 인자로 받아서 2번 반복해준다.

이는 단 두번만 반복하는 함수지만, 인자로 받는 함수 (위 예제에선 plusOne() 함수) 를 통해 반복되는 패턴 등을 제어할 수가 있다.

 

1 - 2. Abstracting Pattern of Control (제어 패턴 추상화)

 

또한, HOF를 통해, 단순히 함수의 값을 전달하는 역할 이외에, 제어 패턴 추상화의 역할도 할 수 있음을 이해해야한다. 

제어 패턴 추상화란, HOF 내의 계산의 세부사항 (반복되는 패턴 등등) 을 인자로 넘기는 함수 안에 포함하여 추상적으로 제공한다.

적으면서 나도 무슨 말인지 이해가 쉽게 되지 않아 예제를 통해 보도록 하겠다 ㅎ..

개념들을 이해하는데 역시나 예제만큼 도움되는 것이 없는 것 같다 :\

 

"0 부터 99 까지 출력하라"는 문제가 주어진다면 우리는 쉽게 아래와 같은 코드를 적을 수 있다.

for (let i = 0; i < 100; i++) {
  console.log(i);
}

하지만 우리는 변형되는 문제들을 계속 마주치게 되는데... 이번엔 "0 부터 9999 까지 출력하라"는 문제가 있다고 보자.

우리는 코드를 단순히 i < 10000 으로 변경할 수도 있지만 이러한 방법은 가독성에도 좋지 않을 뿐더러 수정이 용이하지 않다.

function repeat(n) {
  for (let i = 0; i < n; i++) {
    console.log(i);
  }
}

repeat(10000);

위와 같이 함수의 n값을 통해 출력될 결과값을 쉽게 제어할 수 있다.

 

대게 고난이도 문제나 실무에서 우리가 마주칠 함수는 복잡한 로직을 갖고 있을 텐데, 코드를 수정할 때 항상 부작용에 대한 공포심을 갖고 수정을 해야한다.

 

나는 아직 개발자에 겨우 발을 내딛었을 뿐이지만 시간이 지나고 점점 난이도가 있는 문제를 풀다보니 함수가 한개씩 늘어나기도 하고,

한가지 함수 안에 복잡한 로직이 담겨있어 살짝만 수정해서 운이 좋게 문제의 한 부분은 풀었다해도 윗부분이 다 터지는 일이 쉽상이었다.

 

다시 예제로 돌아와서, 이번에는 단순히 출력만이 아닌 0 부터 9999 까지 배열에 담아야 한다고 보자.

 

현재 함수는 console.log(i)만 있기 때문에 출력을 수행하는 일밖에 없다.

그럼, 첫번째 할 수 있는 것은 console.log(i)를 list.push(i)로 만들면 된다.

 

그러나 for문 안에 로직만 변경한다고 모든 요구사항을 충족할 수는 없다.

출력과 배열에 담는 것, 두 가지를 모두 실행하기 위한 유연한 로직은 바로 우리가 배운 HOF다.

function repeat(n, fn) {
  for (let i = 0; i < n; i++) { 
    fn(i); 
  }
}

//0부터 9999까지 출력하기  
repeat(10000, console.log); 

//0부터 9999까지 배열에 담기
const list = []; 
repeat(10000, (i) => {list.push(i)});

0 부터 9999 까지 출력해야하는 문제는, console.log(i)를 repeat()함수 안의 fn(i)로 변경해서 해결해주었다.

두번째로, 빈 배열을 list라는 변수에 담아, repeat()함수를 실행시켜 모든 값을 list 안에 담아준 것이다.

 

여기서 문제를 조금 더 변형시켜, 아래와 같이 반복되는 횟수 또한 추상화시킬 수도 있다.

function repeat(n, fn, interval) {
  for (let i = 0; i < n; i = interval(i)) { 
    fn(i); 
  }
}

repeat(100, console.log, i => i === 0 ? ++i : i+i); //0 1 2 4 8 16 32 64

* 잠시만 어제 올린 TIL을 참고하겠습니다.

어제, 재귀 함수 (Recursion) 관련해서 추가로 알아보던 중에 다른 사이트에서 본 코드에 사용된 방법이 이해가 되지 않았고,

어떤 키워드로 검색해야할지 도무지 몰라 엔지니어에게 그 코드에 대해 질문드리니 삼항연산자라고 답변을 받았다.

 

내가 어제 작성한 팩토리얼 함수는 이렇다:

function factorial (n) {
  if (n === 1) { //Base case, 탈출 조건
    return 1;
}

  return n * factorial(n - 1);
}

factorial(5); //120

그리고 내가 보고 궁금했던 코드는 이렇다:

function factorial(n) {
  return (n != 1) ? n * factorial(n - 1) : 1;
}

alert( factorial(5) ); // 120

두 방법 모두 factorial(5)를 했을 때, 120 이라는 값이 나왔는데,

두번째 코드의 return 부분에서 사용된 '?'와 ':' 이 두 가지의 역할이 궁금했었다.

이것이 바로 삼향연산자 라는 것이었는데, 내가 보고 한번에 이해가 된 엔지니어의 예시는 이랬다.

1 > 2 ? doSomethingForTrue() : doSomethingForFalse()
//여기에선 1 > 2 가 false이기때문에 뒤에있는 doSomethingForFalse를 실행합니다.

모든 궁금했던 게 풀리면서, 깊은 깨달음을 얻는 '대박...' 순간이었다.


다시 본론으로 돌아와서,

repeat(100, console.log, i => i === 0 ? ++i : i+i); //0 1 2 4 8 16 32 64

 

이런 코드를 이번 TIL의 예제에 직접 사용해볼 수 있게 되어 뿌듯했다.

 

1 - 3. 함수를 리턴하는 함수

여기가 고차함수의 끝이 아니다. 이제 두번째, 함수를 리턴하는 함수를 알아보자.

function fillArray(n, fn) {
  let array = [];
  for (let i = 0; i < n; i++) {
    array.push(fn(i));
  }
  return array;
}

const array = fillArray(7, (i) => `item${i}`);

console.log(array); //['item0', 'item1', 'item2', 'item3', 'item4', 'item5', 'item6']

위 예제의 fillArray(n, fn) 함수는 반복 횟수와 배열에 들어가는 인자를 리턴 하는 함수를 넘겨받아 완성된 Array를 반환한다.

 

함수를 리턴하는 함수를를 더 명시적으로 바꿔볼 수 있다.

function makeItem(i) {
  return (i) => `item${i}`;
}

const array = fillArray(7, makeItem);

맨 앞의 함수와 마지막 줄을 제외한 가운데 부분만 수정하여 makeItem(i) 함수를 만들어주었다.

Expression Language인 `${item}`은 makeItem(i) 클로저 함수 안의 메모리로 남아있게 되어 계속 다른 곳에서도 사용할 수도 있다.

 

지금까지 예제로 본 것처럼, 많은 곳에서 HOF가 사용됨을 알 수 있는데 Filter, Map, Reduce 함수들 또한 HOF임을 알 수 있다.

세 가지 방법 모두 함수를 통해 제어 패턴을 추상화하는 방법이다.

 

HOF를 활용한 라이브러리 또한 여러가지가 있는데, 대표적으로 Lodash, React 그리고 Ramda 가 있다.


이번 TIL을 마치며..

 

사실 이전에 조금 궁금했었고 잘 사용하지 못했던 두 가지 방법이 있다.

  • 화살표 함수 (Arrow Functions) "=>"
  • Expression Language `${}`

오늘 내용들을 다시 공부하면서 위 궁금했던 방법들을 사용해볼 수 있던 점이 이번 TIL을 의미있게 해준 것 같다.

 

다음주면 다시 기초 과정의 처음부터 배우게 될텐데,

앞으로 꾸준히 복습하며 그날의 TIL 또한 올리는 것이 계획이다.

더 나아가 개념을 완벽히 이해하고, 나만의 예제를 더 많이 만들고 싶다.

 

그럼,,, 주말 동안 충분히 휴식하고 다시 새로 시작하기 ㅎㅎ


심화 내용..

 

  • MapReduce (MapReduce Model)
  • 자바스크립트에서 커링 (currying)과 클로져 (closure)의 차이 (js closure vs curry)
  • 선언형 프로그래밍 (declarative programming)과 절차형 프로그래밍 (imperative programming)의 차이를 배열 메소드를 통해 이해 (js imperative vs declarative)
  • 함수의 조합 (function composition)에 대해 학습하기 (javascript function composition)

'Programming > TIL' 카테고리의 다른 글

String Methods  (0) 2020.10.27
변수와 자료형 & 함수 & 조건문  (0) 2020.10.26
Recursion [재귀]  (0) 2020.10.22
textContent vs innerHTML  (0) 2020.10.21
DOM  (0) 2020.10.20