들어가며

함수형 프로그래밍은 명령형 프로그래밍과는 반대의 개념으로 상태를 변경하기 보다는 함수를 통해 해결해나가는 방식이다.

명령형 프로그래밍에서의 함수는 동일한 값을 넣어 호출하더라도, 상태 값에 따라 다른 값을 반환할 수도 있다. 상태에 따라 다른 값을 반환하다 보니, 디버깅이 어려울 뿐만 아니라 예기치 못 한 문제가 발생하는 경우가 있다.

반대로 함수형 프로그래밍에서는 순수 함수, 1급 객체, 참조 투명성등의 특징을 가지고 있는데, 이러한 특징 덕분에 함수에 전달된 인자를 통해서만 결과가 결정된다.

함수형 프로그래밍은 기존 명령형 프로그래밍의 문제점들을 해결하기 위해서, 대부분을 순수 함수로 분리하고자 하는데, 간단한 작업조차도 함수로 작성하여 가독성을 높이고 유지보수를 편하게 만든다.

하지만 모든 함수를 순수 함수로 만들 수는 없어서, 순수 함수와 불순 함수의 비율을 80:20로 작성하는 걸 권장한다. 이 비율은 임의의 값으로 지키지 않아도 되며 순수 함수 사용을 권장한다는 의미로 생각하면 된다.

참조 투명성 (Referential Transparency)

순수 함수에 대해 알기 위해서는 순수 함수의 특성인 참조 투명성에 대해 알아야 한다. 동일한 인자에 대해서는 같은 값을 반환하는 경우, 참조에 투명하다고 볼 수 있다.

예를 들어 ab를 받아 더한 뒤 반환하는 sum이라는 함수가 있다고 가정해보자. 해당 함수는 다른 상태의 값의 영향을 받지 않고, 오직 ab를 통해서만 결과값이 결정되기에 참조 투명하다고 볼 수 있다.

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

sum(1, 2) // 3
sum(1, 2) // 3
1
2
3
4
5
6

이제 참조에 투명하지 않은 함수를 알아보자. 아래 sum 함수는 a를 인자로 받고 Math.random()을 더한 뒤 결과를 반환한다. 똑같이 sum 함수에 5라는 값을 입력하더라도 Math.random()의 결과값에 의해 매번 다른 결과가 나온다. 이런 경우에는 동일한 인자를 넣어도 다른 값을 반환해서, 참조 투명하다고 볼 수 없다.

function sum(a) {
    return a + Math.random();
}

sum(5) // 5.312761858067091
sum(5) // 5.631779221265544
1
2
3
4
5
6

이렇게만 볼 때, 참조 투명성을 위해서는 함수 안에서 어떠한 값들도 참조하지 못 한다고 볼 수도 있는데, 무조건 값을 사용하지 못 하는 것은 아니다.

const discount_rate = 0.9;

function getTotalPrice(price, quantity) {
    return price * quantity * discount_rate;
}
1
2
3
4
5

discount_rate 상수와 같이 변하지 않는 값이 존재할 경우, 함수 안에서 사용하더라도 결과는 인자를 통해서만 결정되니 사용해도 참조 투명하다고 볼 수 있다.

순수 함수 (Fure Function)

함수는 순수 함수와 불순 함수로 나눌 수 있다. 코드를 작성할 때 최대한 순수 함수로 작성하는게 좋지만 아무리 코드를 잘 작성하더라도 불순 함수는 나올 수밖에 없고, 필연적으로 불순 함수의 비율이 높아지는 상황이 생길 수도 있다. 순수 함수는 아래와 같은 특성을 가지고 있으며, 이 특성을 만족하는 함수를 순수 함수라고 부른다.

참조 투명성

앞서 설명한 것처럼 동일한 인자에 대해 같은 값을 반환하는 것을 의미한다. 즉 순수 함수는 다른 변수에 영향을 받지 않고 오로지 인자에 의해서만 결과가 결정되어야 한다.

부수 효과 X

순수 함수는 어떠한 부수 효과(side effect)가 발생하면 안 된다. 여기서 부수 효과란 API 호출, window 이벤트 처리, console 출력, 값 변경, 오류 등을 의미한다.

  • Array
    1. push
    2. splice
    3. unshift
    4. fill
  • Map
    1. set
    2. delete
    3. clear

Array, Map, Object 등과 같이 내부 데이터를 변경하는 것도 부수 효과라고 볼 수 있다.

1급 객체 (First Class Object)

함수형 프로그래밍에서는 순수 함수1급 객체로 취급해서, 인자로 넘기거나 반환이 가능하다고 하다. Javascript에서 함수는 프로퍼티와 메소드를 가질 수 있어서, 객체라고 볼 수 있다.

{} 일반 객체와 다른 점은 호출할 수 있다는 점이다. 이제 1급 객체의 의미와 될 수 있는 조건을 알아보자.

할당 가능한 변수 혹은 데이터

1급 객체가 되기 위해서는 변수 혹은 데이터에 할당할 수 있어야 한다. Javascript에서는 var, let, const에 어떤 타입이라도 할당할 수 있다.

const sum = function(a, b) {
    return a + b;
}

sum(1, 2) // 3

function sub(a, b) {
    return a - b;
}

const subCopy = sub;

subCopy(3, 1) // 2
1
2
3
4
5
6
7
8
9
10
11
12
13

변수를 선언할 때, 주로 문자와 숫자 등을 할당하는데 예제처럼 sum에 익명 함수를 할당해서 사용할 수 있고, 이미 선언된 함수를 새롭게 할당하는 방식으로도 사용할 수 있다.

인자로 전달 가능한 함수

1급 객체가 되기 위해서는 함수의 인자 값으로 사용이 가능해야 하는데, 주로 숫자와 문자를 함수의 인자값으로 사용한다.

const printName = (name) => {
    console.log(`내 이름은 ${name}입니다.`);
};

printName("홍길동"); // 내 이름은 홍길동입니다.

const printAge = (age) => {
    console.log(`내 나이는 ${age}살 입니다.`);
}

printAge(17); // 내 나이는 17살 입니다.
1
2
3
4
5
6
7
8
9
10
11

다만 함수의 경우에는 인자로 넘겨본 경험이 없을 수도 있어서, 이해를 돕기 위해 미리 만들어두었던 sumsub 함수를 사용하여 어떻게 함수를 인자로 넘기는지 코드를 작성했다.

const sum = (a, b) => {
    return a + b;
};

const sub = (a, b) => {
    return a - b;
};

const print = (a, b, func) => {
    console.log(func(a, b));
};

print(5, 3, sum) // 8
print(5, 3, sub) // 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14

sumsub 함수는 두 개의 인자를 넘겨서 연산한 이후에 결과값을 반환하는 함수다. print 라는 함수를 추가로 만들었는데, 이 함수는 subsub처럼 두 개의 인자를 받는 것은 동일하고 세 번째 인자에는 함수를 넘겨서 실행할 수 있다.

sum을 넘길 경우에는 sum(a,b)를 호출한 것과 동일한 값을 출력하고, sub를 넘길 경우에는 sub(a,b)를 호출한 것과 동일하다. 이렇게 인자로 넘겨서 사용 가능해야 1급 객체의 조건 중 한 개를 만족한다.

반환 가능한 함수

함수는 또 다른 함수를 반환할 수 있어야 한다. 어떻게 함수를 반환하고 사용하는지 알아보자.

function multiple(a) {
    return function(b) {
        return a * b;
    }
}

const multiple5 = multiple(5)

multiple5(2) // 10
multiple5(5) // 25
multiple(7)(8) // 56
1
2
3
4
5
6
7
8
9
10
11

multiple 함수는 익명 함수를 반환하는데, 최초 multiple 함수 호출 시 넘긴 인자값과 익명 함수 호출 시 넘긴 인자값을 곱해서 결과값을 반환한다.

부분 적용(partial application)이나 커링 (currying) 기법을 사용할 때 쓰는데, 함수를 반환하면 ()(a, b, ...)이나 ()()()과 같이 사용할 수 있다.

초기에 가지고 있는 정보 혹은 필요한 정보만 전달해서, 함수를 반환 받고 추가로 필요할 때 사용하는 방식으로 사용하면 용이하다. 이를 함수형 프로그래밍에서 lazy evaluation라고 부른다.

Last Updated:
Contributors: dailyuno