December 9th 2018
차례
function이라는 keyword 자체에만 초점을 두면 함수형 프로그래밍이 무엇인지 이해를 하기 어렵습니다. 함수형 프로그래밍이란, 함수를 이용해 애플리케이션을 모델링 하고, 데이터 전달 및 가공에도 함수가 핵심적인 역할을 하는 것을 말합니다.
추상화에 대해 질문을 하면, class 기반의 코딩 또는 어떠한 세부 사항을 숨기는 것이라고 답변을 받을지 모릅니다. 하지만, 이러한 추상화에 대한 정의는 정확하지 않으며, 애초에 추상화가 설계된 목적에 맞지도 않습니다. 세부사항을 숨기는 것은 encapsulation 이라는 다른 용어가 이미 존재합니다. 추상화란 연관성을 가지고 있는 여러 것들을 구분할 수 있도록 의미가 있는 경계를 만드는 것입니다.
순수함수란 동일한 인자가 주어지면 여러번 호출을 해도 항상 같은 결과를 반환하고, 부수효과(Side-effect)가 없는 함수를 말합니다. 일반적으로 프로그래밍을 하며 주변의 변수를 활용하거나, 주변 환경에 영향을 주는 함수를 작성하곤 합니다. 함수형 프로그래밍에서는 이러한 부수효과를 좋지 않은것으로 간주합니다.
이러한 주장에는 개발자마다 여러 이유가 있겠지만, 아마도 부수효과가 있으면 함수를 실행했을 때 결과를 예측하기 어렵게 만들기 때문입니다.
function f() {
y = 2 * Math.pow(x, 2) + 3;
}
var x, y;
x = 0;
f();
y;
x = 2;
f();
y;
위의 코드에서 만약 첫번째로 f함수를 호출하는 코드가 1000줄 또는 2000줄 뒤에 있다고 가정해 봅시다. f함수를 예측하기 위해 이전의 모든 코드를 검토를 해야합니다. 컴퓨터는 코드를 쉽게 읽을 수 있겠지만 만약 주변의 동료가 이러한 코드를 읽어야 한다면, 이것은 큰 문제가 될 수 있습니다. 코드를 다 읽기도 힘들뿐더러 실수없이 읽는것은 더 힘들기 때문입니다. 부수효과가 있는 코드는 어렵지 않게 작성할 수 있지만, 다른 사람이 그 코드를 읽기는 더 어렵습니다.
따라서, 부수효과가 없는 코드를 작성하는 것이 코드의 가독성을 더 좋게할 수 있습니다. 또한, 부수효과가 최소한이면 버그가 발생했을때 부수효과가 있는 부분을 먼저 검토할 수 있기때문에 유지보수에도 좋습니다.
하지만 사실상 Side-effect없이 프로그램을 만드는 것은 불가능합니다. 만약 Side-effect가 없다면 DOM에 요소를 추가할수도 없고, 네트워크 요청도, 파일 시스템을 변경할 수도 없습니다. 따라서, 코드를 작성할 때 가능한 최소한의 Side-effect를 만들고, Side-effect가 있는 부분을 알아보기 쉽게 코드를 작성하는것이 좋습니다.
var y;
y = f(0); // 3
y = f(2); // 11
y = f(-1);
앞에서도 언급했지만 가능하면 Side-effect가 없는 코드를 작성하는것이 좋습니다. 하지만, 이러한 코드를 작성하는 것이 어렵거나 우리가 제어할 수 없는 라이브러리 함수를 다루는 경우가 있을 수 있습니다. 이러한 경우에 Side-effect를 최소화하는 몇가지 방법이 있습니다.
그러면 Side-effect가 없는 순수함수를 얻는 방법에 대해 알아보도록 하겠습니다. 아래의 코드는 순수함수가 아닌 함수를 순수함수화 하는 방법입니다. 함수 f는 lexical scope의 변수 x를 참조하고 있습니다. 만약 이 변수를 숨기거나 캡슐화하고자 한다면 어떻게해야 할까요?
```javascript
function f() {
y = 2 * Math.pow(x, 2) + 3;
}
var x, y;
x = 0;
f();
y;
x = 2;
f();
y;
캡슐화 함수 f주변에 변수 x를 포함한 새로운 lexical scope를 생성하는 방법이 있습니다. 아래의 코드는 대문자 F함수를 새로이 만들고 소문자 f함수가 참조하는 새로운 lexical scope를 만들었습니다.
F함수 내부에서 f함수가 실행이 되면, f함수는 Side-effect가 발생하지만 이 Side-effect는 F함수 내부에서만 발생합니다. 따라서, 인자 0과 함께 실행한 F함수는 함수 자체의 observable behaviour 덕분에 순수함수가 될 수 있습니다. 함수에 0이 주어지면 항상 동일한 결과인 3을 반환받습니다.
function F(x) {
var y;
f(x);
return y;
function f() {
y = 2 * Math.pow(x, 2) + 3;
}
}
var y;
y = F(0); // 3
y = F(2); // 11
여기서 눈여겨 생각해봐야 할 점은 외부함수로 감싸서 문제를 해결했다는 점이 아닙니다. 요지는 함수형 프로그래밍에서 Side-effect와 Side-causes가 없는 순수함수의 중요성을 인지하고, F함수가 독립적으로 작동할 수 있어 순수함수가 된 점입니다.
재사용 가능한 유틸리티를 생성하고자 하는 관점에서 본다면, Side-effects는 내부적으로 모두 포함하고 있기 때문에, F함수 내부가 어떻게 작동하던지 문제가 없습니다. 하지만, 만사가 그렇든 위의 코드에서 tradeoff가 존재합니다.
F함수의 내부가 복잡해질수록 코드를 읽고 f함수를 이해하는 일이 어려워집니다. 따라서, 이러한 변화가 가독성을 떨어뜨리는 것보다 가치가 있는지 고려를 해봐야합니다.
여기까지 부수효과를 캡슐화하고 외부에서 관찰가능하게??(obserable) 만들어 문제를 해결한 방법에 대해 알아보았습니다.
Interface Function 또다른 방법으로는 인터페이스 함수를 작성하는 것입니다. 아래의 코드에서 함수 F의 역할은 현재 상황을 캡쳐링한 후, 부수효과가 있는 함수를 실행하고 그 결과를 또다른 변수에 저장합니다. 그리고 최종적으로 side-effect가 없던 이전의 상태로 주변의 환경을 되돌리고 변수에 저장했던 결과값을 반환합니다.
function f() {
y = 2 * Math.pow(x, 2) + 3;
}
function F(curX) {
var [origX, origY] = [x, y];
x = curX;
f();
var newY = y;
[x, y] = [origX, origY];
return newY;
}
var x, y;
F(0); // 3
F(2);
위와 같이 진행함으로써 함수 F는 부수효과가 발생하지 않는 함수가 되었습니다. 위의 예는 변수 두 개만을 설정하지만, 실제 상황에서는 매우 많은 변수 또는 DOM을 조작하는 등의 부수효과가 발생할 수 있습니다. 이러한 경우에는 부수효과가 있는 함수를 실행 후 본래의 환경으로 되돌리는것이 쉽지는 않을 것입니다. 따라서, 함수를 순수화 하는것이 충분히 가치가 있는지 고려해 보아야 할것입니다.
아래의 예제를 통해 순수함수화 함으로 캡슐화와 인터페이스 방법을 좀 더 연습해 보았습니다. 아래에는 foo라는 impure function이 있습니다. foo함수 내부에서 y++ 코드로 y값을 변경하고, 변경한 값이 z값에 영향을 주기 때문에 side-cause 겸 side-effect가 됩니다. 그리고 그 밑의 줄은 연산 후 외부 변수 z의 값을 변경하기 때문에 side-effect가 됩니다. 총 1개의 side-cause와 2개의 side-effect를 가집니다.
function foo(x) {
y++;
z = x * y;
}
var y = 5,
z;
foo(20);
z;
console.log(z); // 120
foo(25);
z;
console.log(z); // 175
변수 z를 내부에 정의한 wrapper 함수 bar를 생성했고, bar는 argument로 x와 y를 받습니다. 그리고 함수 foo가 실행된 후의 새로운 y, z 값을 배열로 반환했습니다. 이러한 경우 우리는 변화하는 y의 값을 추적하고 다음 함수 호출 시 사용할 수 있습니다.
이렇듯 함수형 프로그래밍에서도 state change가 있습니다. 다만 이러한 변화를 분명하고 명시적으로 파악할 수 있고, 변화가 발생했을때 그것을 이용할지 선택하도록 프로그래밍합니다.
function bar(x, y) {
var z;
foo(x);
return [y, z];
function foo(x) {
y++;
z = x * y;
}
}
bar(20, 5);
bar(25, 6);