JavaScript - 함수형 프로그래밍

박정호·2022년 10월 4일
0

JS

목록 보기
16/24
post-thumbnail

🚀 Start

🧐 함수란 무엇인가? 자바스크립트에서 함수는 일급 객체이다.

함수는 다음과 같은 일급객체의 조건을 만족한다.

  • 함수는 무명의 리터럴로 생성할 수 있다.
    -> 런타임(할당 단계)에 함수 리터럴이 평가되어 함수 객체가 생성되고 변수에 할당된다.

  • 함수를 변수나 자료구조(배열의 요소나 객체의 속성값)에 할당 할 수 있다.

  • 함수를 다른 함수의 인자로 전달될 수 있다.

  • 함수를 다른 함수의 결과로서 리턴될 수 있다.

⭐️ 결국 함수는 객체처럼 사용이 가능하다는 말이다. 변수, 배열요소, 객체 값에 할당하거나 또 다른 함수의 인수로 전달되거나 함수에서 반환될 수도 있다.

이는 함수형 프로그래밍을 가능하게 하는 자바스크립트의 장점 중 하나이다. 함수 객체가 일반 객체와 가장 큰 차이점은 함수는 호출할 수 있다는 것이다. 그리고 함수 객체는 일반 객체에 없는 함수 고유의 프로퍼티를 소유한다.

💡 잠깐) Property

: 프로퍼티란 속성이란 뜻으로 자바스크립트에서 객체 내부의 속성을 의미.

객체는 프로퍼티로 구성되고, 프로퍼티는 "key(키)" : "value(값)" 의 형식으로 객체 안의 콤마로 구분되어 할당된다.

  • Key는 속성명이라 할 수 있고 문자열만 가능하며, 문자열이지만 따옴표가 없어도 된다.

  • Value는 속성값이라고도 부르며, 어떤 값이든지(문자열, 숫자, 객체, 함수 등 아무거나) 상관없다.

객체에 함수를 정의할 경우, 속성값이라 하지않고 메소드(Method)라고 부른다.

🌐 프로그래밍 패러다임

프로그래밍 패러다임은 프로그래머에게 프로그래밍의 관점을 갖게 하고 코드를 어떻게 작성할지 결정하는 역할을 한다.

프로그래밍 패러다임은 크게 두가지로 구분된다.

✏️ 명령형 프로그래밍: 무엇(What)을 할 것인지 나타내기보다 어떻게(How) 할 건지를 설명하는 방식

  • 절차지향 프로그래밍: 수행되어야 할 순차적인 처리 과정을 포함하는 방식 (C, C++)

  • 객체지향 프로그래밍: 객체들의 집합으로 프로그램의 상호작용을 표현 (C++, Java, C#)

✏️ 선언형 프로그래밍: 어떻게 할건지(How)를 나타내기보다 무엇(What)을 할 건지를 설명하는 방식

  • 함수형 프로그래밍: 순수 함수를 조합하고 소프트웨어를 만드는 방식 (클로저, 하스켈, 리스프)

⭐️ 함수형 프로그래밍

위와 같이 프로그래밍 패러다임 중 하나인 함수형 프로그래밍은 현재 가장 관심을 받는 패러다임이다.

👍 함수형 프로그래밍은 순수함수로 나누어 문제를 해결하는 방법으로, 작은 문제를 해결하기 위한 함수를 작성하여 가독성을 높이고 유지보수를 용이하게 한다.

  • 순수함수를 만드는 것
  • 모듈화 수준을 높이는 것
  • 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임
  • 부수 효과(Side Effect)를 없애고 함수끼리 조합성을 강조하는 프로그래밍 패러다임
  • 함수 외부 데이터에 의존하지 않고, 외부 데이터를 변경하지 않는다.

예시

  1. 클로저: add_maker 호출이 끝난 시점에도 변수 a에 접근할 수 있으므로 add_maker 내부 리턴문에 있는 함수는 클로저이다.

  2. 일급 함수: 함수를 리턴하고 있으므로 일급 함수 개념이 쓰였음을 알 수 있다.

  3. 순수 함수: add_maker 내부에서 리턴하는 함수는 순수 함수이다. a라는 변수를 참조할 뿐 변경하고 있지 않다. 항상 동일한 값을 가리키는 a라는 값에 b를 더하는 순수 함수인 것이다. 또한 add10 와 같이 함수를 만들고 나면 어느 시점에 호출을 하던 일관성 있는 결과를 낸다.

function add_maker(a) {
  return function (b) { //3 번
    return a + b; // 1번
  };
}
>
const add10 = add_maker(10); // 2번
console.log(add10(20)); // 30
>
const add5 = add_maker(5);
console.log(add5(10)); // 15
>
const add15 = add_maker(15);
console.log(add15(10)); // 25

💡 중요) 부수효과 & 순수함수 & 일급함수

✏️ 부수효과(Side Effect)

다음과 같은 변화 또는 변화가 발생하는 작업을 의미

  • 변수의 값이 변경됨
  • 자료 구조를 제자리에서 수정함
  • 객체의 필드값을 설정함
  • 예외나 오류가 발생하며 실행이 중단됨
  • 콘솔 또는 파일 I/O가 발생함

✏️ 순수 함수(Pure Function)

부수 효과(Side Effect)들을 제거한 함수들을 순수 함수(Pure Function)이라고 부르고,수형 프로그래밍에서 사용하는 함수가 이러한 순수 함수들이다.

  • Memory or I/O의 관점에서 Side Effect가 없는 함수
  • 동일한 입력에는 항상 같은 값을 반환해야 하는 함수
  • 함수의 실행이 프로그램의 실행에 영향을 미치지 않아야 하는 함수

순수함수 X

  • add라는 함수 안에서 전역으로 선언된 변수인 num을 참조하기 때문에 순수함수가 아니다.
let num = 1;

function add(a) {
    return a + num;
}

순수함수 O

  • add의 함수가 프로그램 실행에 영향을 미치지 않고 입력 값에 대해서만 값을 변환이 일어나므로 순수함수이다.
function add(a, b) {
    return a + b;
}

const result = add(2, 3);

✏️ 일급 함수(객체)(First-Class Object)

자바스크립트에서는 함수를 값으로 다룰 수 있다. 다음과 같은 조건을 만족하기 때문에 자바스크립트에서 함수를 일급 함수라고 하기도 한다.

  • 함수를 변수에 담을 수 있다.
  • 함수를 매개변수(파라미터)로 넘길 수 있다.
  • 함수를 함수에서 반환할 수 있다.
  • 할당에 사용된 이름과 관계없이 고유한 구별이 가능하다.
  • 동적으로 프로퍼티 할당이 가능하다.

Ex) 함수를 변수에 할당하거나 반환하는 특징을 가지는 예시

// 1급 객체
const addTwo = (num) => num + 2;
const multiplyTwo = (num) => num * 2;
const transform = (numbers) => numbers.map(addTwo).map(multiplyTwo);

console.log(transform([1, 2, 3, 4])); // [6, 8, 10, 1

👍함수형 프로그래밍 장점

  • 높은 수준의 추상화를 제공한다
  • 함수 단위의 코드 재사용이 수월하다
  • 불변성을 지향하기 때문에 프로그램의 동작을 예측하기 쉬워진다

👎 함수형 프로그래밍 단점

  • 순수함수를 구현하기 위해서는 코드의 가독성이 좋지 않을 수 있다
  • 함수형 프로그래밍에서는 반복이 for문이 아닌 재귀를 통해 이루어지는데 (deep copy), 재귀적 코드 스타일은 무한 루프에 빠질 수 있다
  • 순수함수를 사용하는 것은 쉬울 수 있지만 조합하는 것은 쉽지 않다

⭐️ 고차함수(HOF)

앞서 바라본 함수형 프로그래밍의 개념에는 고차함수(high order function)이 존재한다.

고차함수는 다른 함수를 인자(Argument)로 받거나 또는 함수를 반환하거나 두 가지 모두를 수행하는 함수이다.

💡Array.map, Array.filter, Array.reduce와 같은 고차함수들이 존재한다.

자세한 설명 -> 고차함수 종류

✏️ 다른 함수를 인자로 받는 경우

function double(num) {
  return num * 2;
}
// doubleNum 함수는 고차 함수이다. (다른 함수를 인자로 받음)
// doubleNum 함수의 인자 func에 함수가 들어올 경우
// func은 doubleNum의 콜백 함수이다.
function doubleNum(func, num) {
  return func(num);
}
// 아래의 경우 double은 doubleNum의 콜백 함수이다.
doubleNum(double, 5);

✏️ 다른 함수를 반환(return)하는 경우

// subtractor 함수는 고차 함수이다. (다른 함수를 리턴한다.)
function subtractor(subtract) {
  return function (num) {
    return num - subtract;
  }
}
// subtractor(5)는 함수이므로 호출 연산자 '()'를 사용 할 수 있다.
subtractor(5)(8) // --> 3
// subtractor 함수가 리턴하는 함수를 변수에 저장할 수 있다.
// 이는 자바스크립트에서 함수는 일급 객체이기 때문이다.
const subtract5 = subtractor(5);

✏️ 함수를 인자로 받고, 함수를 반환하는 경우

function double(num) {
  return num * 2;
}
// doubleSubtractor 함수는 고차 함수이다.
function doubleSubtractor(subtract, func) {
  const doubled = func(subtract);
  return function (num) {
    return num - doubled;
  }
}
// double 함수는 doubleSubtractor 함수의 콜백으로 전달되었다.
doubleSubtractor(5, double)(18); // --> 8

💡 중요) 콜백함수 & 커리함수

✏️ 콜백 함수

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

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

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

사용이유?

  • 함수의 재활용: 원하는 함수들을 조합해서 사용할 수 있다.
  • 하나 이상의 인수의 함수를, 하나의 인수를 받는 함수로 축소할 수 있다. 그래서 더 가벼운 함수 제작이 가능하다.

Ex) add함수를 재활용 해보자.

Currying X

      function add(a,b){
        return a+b;
    }

    //다음과 같이 재활용

    function addTwo(a){
        return add(a, 2);
    }
    function addThree(a){
        return add(a, 3);
    }
    function addFour(a){
        return add(a, 4);
    }

Currying O

  • addX함수를 통해서 여러 파생 함수 생성이 가능하다.
    function addX(x){
      return function(a){
        return add(a, x);
      }
    }

    const addTwo = addX(2);
    const addThree = addX(3);
    const addFour = addX(4);

    addTwo(2) // 2+2 = 4
    addThree(5) // 3+5 = 8
    addFour(1) // 4+1 = 5

addX함수 동작

  • 인자 x를 받아서 익명함수를 반환한다.
    그 함수는 function(a){return add(a,x);} 형태이다.
    반환받은 값인 함수는 인자 a를 요구한다. a를 넣어주면 add(a,x)의 실행 결과를 반환한다.
    이처럼 함수 실행을 위한 인자를 한번에 받지 않고, 여러 차례에 나눠서 받을 수 있다.

이 예시를 화살표 함수로 나타내면 더욱 간단해진다.

function addX(x){
   return function(a){
     return add(a, x);
   }
 }

 const addX = x => a => add(a,x);

🧐 고차함수 사용이유

추상화의 관점에서 고차 함수가 갖는 이점

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

  • CPU는 0과 1만 이해한다. 코드가 해석되는 복잡한 것들은 자바스크립트 엔진이 대신해준다.
    따라서, 자바스크립트 문법(syntax)을 올바르게 사용하는 것만으로, 다양한 프로그램을 비교적 쉽게 작성할 수 있다. 이처럼 고민거리가 줄어들고, 문제 해결이 더 쉬워지는 것이 추상화의 이점이다.

✏️ 함수라는 추상화

  • 프로그램 작성 시 반복되는 로직은 별도의 함수로 작성하는 것 역시 추상화의 좋은 사례이다.
    추상화의 관점에서 함수를 바라보면, 함수는 사고(thougth) 또는 논리(logic)의 묶음이다.

✏️ 고차 함수는 함수 추상화를 한 단계 높인 것
함수를 통해 얻은 추상화를 한 단계 높인 것이 고차 함수이다.고차 함수는 추상화의 수준을 사고의 추상화 수준으로 끌어올린다. 다시 말하자면, 고차 함수를 통해 보다 높은 수준(higher order)에서 생각할 수 있다.

  • 함수 = 값을 전달받아 값을 리턴한다. = 값에 대한 복잡한 로직은 감춰있다. = 값 수준에서의 추상화
  • 값 수준의 추상화: 단순히 값(value)을 전달받아 처리하는 수준
  • 사고의 추상화: 함수(사고의 묶음)을 전달받아 처리하는 수준
  • 고차함수 = 함수를 전달받거나 함수를 리턴한다. = 사고(함수)에 대한 복잡한 로직은 감춰있다. = 사고 수준에서의 추상화
  • 추상화의 수준이 높아진 만큼 생선성도 높아진다.

참조 및 참고하기 좋은 사이트

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글