[JavaScript] 함수형 프로그래밍(Functional Programming)

Noma·2021년 9월 21일
0
post-custom-banner

1. 프로그래밍 패러다임

  • 명령형: 프로그래밍의 상태와 상태를 변경시키는 구문의 관점에서 연산을 설명하는 방식
    • 절차지향: 수행되어야 할 연속적인 계산 과정을 포함하는 방식(C, C++)
    • 객체지향: 객체들의 집합으로 프로그램의 상호작용을 표현(C++, Java, C#)
  • 선언형: 어떤 방법으로 해야 하는지(How)보다 무엇(What)과 같은지를 설명하는 방식
    • 함수형: 순수 함수를 조합하여 소프트웨어를 만드는 방식(클로저, 하스켈, 리스프)

1.1 명령형 vs. 선언형 프로그래밍

  • 명령형: 알고리즘을 명시하고 목표는 명시 안함
  • 선언형: 알고리즘을 명시하지 않고 목표만 명시

즉, 명령형 프로그래밍은 무엇을 "어떻게" 할 것인가에 가깝고, 선언형 프로그래밍"무엇을" 할 것인가와 가깝다.

실세계의 예제를 살펴보면 이해가 쉽다.

나는 회사에서 열심히 일하고 퇴근한 뒤 A에서 가족과 외식을 하기로 했다. A에 도착한 뒤 안내 데스크에 다가가 말한다.

  • 명령형 방식(How): "12번 테이블 자리가 비었습니다. 우리 가족은 저자리로 걸어가 앉을 것입니다."
  • 선언형 방식(What): "4명 앉을 자리를 부탁해요."

위 예시를 보면, 명령형 방식은 내가 실제로 자리에 "어떻게" 앉을지에 관심이 있다. 따라서 "어떻게" 자리에 앉을지에 대한 단계를 하나 하나 "나열"해야 한다.

반대로 선언형 방식은 내가 "무엇을" 원하는지에 더 집중되어 있다.

예시 코드

// 명령형
function double (arr) {
  let results = []
  for (let i = 0; i < arr.length; i++){
    results.push(arr[i] * 2)
  }
  return results
}

function add (arr) {
  let result = 0
  for (let i = 0; i < arr.length; i++){
    result += arr[i]
  }
  return result
}

$("#btn").click(function() {
  $(this).toggleClass("highlight")
  $(this).text() === 'Add Highlight'
    ? $(this).text('Remove Highlight')
    : $(this).text('Add Highlight')
})


// 선언형
function double (arr) {
  return arr.map((item) => item * 2)
}

function add (arr) {
  return arr.reduce((prev, current) => prev + current, 0)
}

<Btn
  onToggleHighlight={this.handleToggleHighlight}
  highlight={this.state.highlight}>
    {this.state.buttonText}
</Btn>

2. 함수형 프로그래밍이란?

부수 효과(side effect)를 없애고 순수 함수의 조합성을 강조하는 프로그래밍 패러다임이다. 함수형 프로그래밍은 명령형(imperative)이 아닌 선언형(declarative)이며, 애플리케이션의 상태는 순수 함수를 통해 전달된다.

2.1 원칙

  • 모든 데이터는 변경이 불가능해야 한다. (함수 외부의 데이터를 변경하지 않음)
  • 함수는 순수 함수로 만든다.
  • 함수와 데이터를 중점으로 생각한다.
  • 반복문 대신에 재귀함수를 사용한다.

2.2 장점

  1. 사이드 이펙트가 없다.
    순수함수의 조합으로 이루어지기 때문에 결과값은 변하지 않는다.

  2. 간결하다.
    함수형 프로그래밍의 중요 개념인 Curry, Partial Application, Monad와 같은 기법이 간결하고 우아한 함수의 구성(Composition)을 가능하게 해준다.

2.3 단점

  1. 상태(State)가 없다.
    함수형 프로그래밍은 상태(State)를 배제하여 Side Effect가 없게 동작한다(연계성이 없다는 소리). 그러나 프론트에서 상호작용(Interaction)은 대부분 상태 변화로 모델링된다.

단, 현재 React에서는 hooks의 state를 통해 함수형 프로그래밍 방식으로도 상태를 가질수 있다.

3. 함수형 프로그래밍 관련 개념

3.1 1급 객체(First Object)

1급 객체란 다음과 같은 조건을 만족하는 객체를 말한다.

  • 변수나 데이터 구조 안에 담을 수 있다.
  • 파라미터로 전달할 수 있다.
  • 반환값으로 사용할 수 있다.
  • 할당에 사용된 이름과 관계없이 고유한 구별이 가능하다.
  • 동적으로 프로퍼티 할당이 가능하다.

자바스크립트에서 함수(Function)은 1급 객체이다.

3.2 고차 함수

고차 함수는 아래 조건을 만족하는 함수를 말한다.

  • 함수에 함수를 파라미터로 전달할 수 있다.
  • 함수의 반환값으로 함수를 사용할 수 있다.

고차 함수는 1급 함수의 부분 집합이다. 리액트 고차 컴포넌트(HOC)는 위의 조건을 만족하는 컴포넌트를 말한다.

3.3 불변성(Immutabiliy)

함수형 프로그래밍에서는 함수 외부의 데이터를 변경하지 않도록 한다. 만약 데이터 변경이 필수적일 경우, 원본 데이터를 변경하지 않고 그 데이터의 복사본을 만들어 변경 작업을 진행한다.

const red={name:'red'};

// 원본 데이터를 변경하는 방법💩
funcion changeColor(color,name){
   color.name=name;
   return color;
}
console.log(changeColor(red,'yellow')); //{name:'yellow'}
console.log(red); //{name:'yellow'}

// 불변성을 지키는 방법👍
function changeColor(color,name){
   return Object.assign({},color,{name});
}
console.log(changeColor(red,'yellow')); //{name: 'yellow'}
console.log(red.name); //{name: 'red'}

아래의 메소드들은 함수형 프로그래밍 개념에 따라 기존 변수에 대한 사이드 이펙트가 없도록 구현되어 있다. 해당 메소드들을 사용하여 데이터의 복사본을 변경하는 방식으로 데이터의 불변성을 지켜주자.

  • map
const arr = ['foo', 'hello', 'diamond', 'A'];
const arr2 = arr.map((v) => v.length);
console.log(arr2) // [3, 5, 6, 1]
  • filter
const arr = [4, 15, 377, 395, 400, 1024, 3000];
const arr2 = arr.filter((v) => (v % 5 === 0));
console.log(arr2) // [15, 395, 400, 3000]
  • reduce
let arr = [9, 2, 8, 5, 7];
let sum = arr.reduce((pre, val) => pre + val);
console.log(sum)	// 31 

3.4 순수함수

아래의 조건을 만족하는 함수를 순수함수라고 한다.

  • 하나 이상의 인자를 반드시 받아야 한다.
  • 반환값이 반드시 존재해야 한다.
  • 함수 내에서 인자를 제외한 다른 변수를 사용하면 안된다.
  • 동일한 입력에는 항상 같은 값을 반환해야 한다.
  • 사이드 이펙트가 없어야 한다. → 함수의 실행은 프로그램 실행에 영향을 미치지 않아야 하며, 개발자가 모든 것을 예측하고 통제할 수 있어야 한다.
    (ex. 함수 내부에서 인자의 값(원본 데이터)을 변경하거나 프로그램 상태를 변경하지 않아야 함)

예시

  • 순수함수가 아닌 함수
const arr = [1, 2, 3, 4, 5];
const condition = function(x) { return x % 2 === 0; }
const ex = function(array) {
  return array.filter(condition); //인자로 받지 않은 변수를 사용하고 있음
};
ex(arr); // [2, 4]
  • 순수함수
const arr = [1, 2, 3, 4, 5];
const condition = function(x) { return x % 2 === 0; }
const ex=function(array,cond){
   return array.filter(cond);
}
ex(arr,condition);

3.5 합성 함수

합성 함수는 둘 이상의 함수를 조합한 함수를 말한다.

함수형 프로그래밍은 여러 작은 순수 함수들로 이루어져있기 때문에 이 함수들을 연쇄적으로 또는 병렬로 호출하여 더 큰 함수를 만드는 과정으로 전체 프로그램을 구축해야 한다.

const sum=(a,b)=>a+b;
const square=x=>x*x;
const addTen=x=>x+10;

const computeNumbers=addTen(square(sum(3,5)))//74

//compose는 함수를 연쇄적으로 호출하면서 반환값을 전달한다.
const compose=(...fns)=>fns.reduce((prev,next)=>(...args)=>next(prev(...args)));

//compose 사용
const compute=compose(sum, square, addTen);
compute(3,5) //74

3.6 반복문보다 재귀함수

반복문을 사용하지 말라고 했는데, 그러면 어떻게 처리해야할지 예제를 통해 살펴보자.

보통 1부터 10까지 더할 때 아래와 같이 많이 한다.

let sum=0;
for (let i=1;i<=10;i++){
	sum+=i;
}

함수형 프로그래밍에서는 다음과 같이 재귀함수를 사용하면 된다.

function add(sum,count){
  if(count>0){
    sum+=count;
  	return add(sum,count-1);
  }else{
  	return sum;
  }
}
add(0,10); //55

위와 같이 add 안에서 add를 또 호출하는 게 복잡해 보일 수 있으나, 실행 결과를 생각해봤을 때 이렇게 하면 코드의 재사용성이 매우 높아진다.

다른 방법으로 reduce 메소드를 쓸 수도 있다.

const arr = [1,2,3,4,5,6,7,8,9,10];
arr.reduce((prev, cur)=> prev + cur); // 55

📚 Reference

profile
오히려 좋아
post-custom-banner

0개의 댓글