본문 바로가기
  • soldonii's devlog
Javascript 공부/Advanced Javascript(-)

자바스크립트의 함수형 프로그래밍 1 : 순수 함수란?

by soldonii 2019. 10. 22.

*Udemy의 "Advanced Javascript Concepts"강의에서 학습한 내용을 정리한 포스팅입니다.

*자바스크립트를 배우는 단계라 오류가 있을 수 있습니다. 틀린 내용은 댓글로 말씀해주시면 수정하겠습니다. 감사합니다. :)


1. 순수 함수(Pure Functions)

순수 함수란,
1. 함수 외부의 그 어떤 데이터(state)도 변경시키지 않는 함수(no side effect)
2. 동일한 input이면 언제나 동일한 output을 리턴하는 함수
const array = [1,2,3];
function a(arr) {
  arr.pop();
}

function b(arr) {
  arr.forEach(item => arr.push(1));
}
a(array);
console.log(array); // [1,2]
b(array);
console.log(array); // [1,2,1]

a함수와 b함수는 함수 외부에 존재하는 데이터(state)인 array의 배열에 변형을 가하고 있다. 이러한 경우는 순수 함수라고 부를 수 없다. 함수형 프로그래밍에서 state의 변경을 최대한 지양하는 이유는, 그렇게 함으로써 예상치 못한 버그를 예방할 수 있기 때문이라고 한다. 순수 함수의 각 개념에 대해서 조금 더 깊게 살펴보자.

 

# No Side Effect

const array = [1,2,3];
function removeLastItem(arr) {
  const newArray = [].concat(arr);
  newArray.pop();
  return newArray;
}

console.log(removeLastItem(array)); // [1,2]
console.log(array);// [1,2,3]

removeLastItem 함수 내부를 보면, 함수 외부의 원본 데이터를 로컬 변수로 그대로 복사한 후, 복사한 배열에 변형을 가해서 리턴시키고 있다. 이 경우 원본 배열인 array는 그대로 있으면서, 함수 실행 후 원하는 결과값을 얻은 것을 확인할 수 있다. 

 

function a() {
  console.log('hi');
}

a 함수의 경우에도 엄밀히 말하면 순수 함수는 아니다. 함수 외부의 브라우저의 콘솔에 변형을 가하고 있기 때문이다.

 

# Same Input, Same Output

function a(num1, num2) {
  return num1 + num2;
}
a(3, 4);

a 함수의 경우, 만약 input이 3, 4로 동일하다면, 이 함수를 1억번을 실행시켜도 결과값은 항상 7로 동일하다. 따라서 순수 함수라고 볼 수 있다. 그리고 a(3, 4)를 결과값 7로 바꿨을 때, 프로그램의 동작에 어떠한 영향도 미치지 않는데 이러한 경우를 Referential Transparency라고 부른다.

// Referential Transparency
function a(num1, num2) {
  return num1 + num2;
}

function b(num) {
  return num*2;
}
b(a(3, 4)); // 14
b(7); // 14

 

# 모든 함수가 순수 함수일 수는 없다.

모든 함수가 순수 함수라면, 모든 함수가 함수 외부의 어떤 데이터에도 변형을 주지 않기 때문에 프로그램은 구동되지 않고 원 상태 그대로만 있을 것이다. 따라서 함수형 프로그래밍의 목적은 모든 함수를 순수 함수로 만드는 것이 아닌, side effect, 즉 외부 state의 변화를 최소한으로 유지함으로써 함수 실행의 결과 예측을 용이하게 하여 버그 발생의 가능성을 줄이는 것이다. 

함수형 프로그래밍에서 추구하는 함수의 이론적인 특징은 아래와 같다.

1. 1 Task : 하나의 함수는 하나의 일만 한다.
2. Return Statement : 반드시 어떠한 결과를 리턴한다.
3. Pure : 순수 함수를 지향한다.
4. No Shared State : 외부 데이터(state)를 공유하지 않는다.
5. Immutable State : Global의 state에 변형을 가하지 않는다.
6. Composable : 여러 개의 함수를 순차적 흐름으로 구성한다.(?) -> 뒤 쪽 compose 부분에서 자세히 다룬다.
7. Predictable : 함수 실행의 결과가 예측 가능하다.

 

2. Idempotence

한국어 번역을 보니 멱등법(?) 이라고 하는데 난생 처음 들어보는 말이라 그냥 영어 원문 그대로 정리하겠다.(ㅎㅎ) Idempotence는 앞서 말한, same input => same output을 리턴시키는 것을 의미한다.

function notGood(num) {
  return Math.random(num);
}
notGood(5);

notGood 함수는 함수가 실행될 때마다 0~1 사이의 난수를 리턴시키기 때문에 동일한 숫자를 input해도 output은 항상 다르다. 따라서 이 함수는 idempotent하지 않은 함수이다.

 

function good(num) {
  console.log(num);
}
good(5);

good 함수는 동일한 input에 항상 동일한 output이므로 idempotent한 함수이다. 

 

idempotent와 pure는 동일한 개념이 아님을 헷갈리지 말아야 한다. 위 good 함수는 idempotent하지만 pure하지는 않다. 무슨 의미냐면, 동일한 input에 동일한 output이기는 하지만, 함수 외부의 브라우저 콘솔에 변형을 가하고 있기 때문에 pure의 첫번째 조건을 충족시키지는 못하는 것이다.

 

Idempotent의 두번째 특징은, 하나의 함수를 계속 중첩시켜서 여러 차례 실행해도 항상 같은 결과물을 리턴한다는 점이다.

Math.abs(-50); // 50
Math.abs(Math.abs(-50)); // 50

Math.abs() 함수의 결과물을 Math.abs()로 한 번 더 중첩시켜서 실행했는데, 여전히 결과물은 동일하게 50이다.

 

3. Imperative vs. Declarative

- Imperative Code : 기계에게 무엇을, 어떻게 해야 할지 명시적으로 알려주는 코드이다.

- Declarative Code : 기계에게 무엇을 해야하는지, 그리고 어떤 결과물을 얻어야 하는지를 알려주는 코드이다.(어떻게 해야 할지는 알려주지 않는다.)

Imperative 할수록 기계와 더 친밀한 언어이며(ex. Machine Code), Declarative 할수록 사람과 더 친밀한 고차원 언어(ex. Javascript, Python 등)이다.

// Imperative for loop
for (let i = 0; i < 1000; i++) {
  console.log(i);
}

// Declarative for loop
[1, 2, 3].forEach(item => console.log(item))
// 어떻게 하라고 말하지 않은, 더 declarative한 코드이다.

 

함수형 프로그래밍은 프로그래머가 더 Declartive한 코드를 작성하도록 도와준다. 최근 가장 핫한 라이브러리인 리액트도 프로그래머가 코드를 declartive하게 쓰도록 도와준다. 다만 오해하지 말아야 할 것은 아무리 코드를 declarative하게 작성해도, 결국 코드가 실행되는 과정에서는 컴파일러에 의해서 imperative하게 변형되고 이를 기계가 처리하게 된다. 리액트도 마찬가지이다. React Libarary가 declarative한 코드를 imperative하게 변경하여 원하는 결과물을 출력한다.

 

4. Immutability

Immutability는 data(state)를 변경시키지 않는다는 의미이다. 대신 state의 copy 버전을 만들고 이를 활용한다.

const obj = {name: "Andrei"};
function clone(obj) {
  return {...obj}; // this is pure.
}
obj.name = 'hyunsol'; // 이 경우 state를 변경시키고 있다.

clone 함수는 함수 자체로만 봤을 땐 순수 함수이다. 함수 외부의 어떤 결과물도 변형시키지 않고, 동시에 같은 input이면 같은 output을 리턴하기 때문이다. 하지만 결국 위 코드는 원본 obj state를 변경시키고 있으므로 immutable하지 않다.

 

const obj = {name: "Andrei"};
function clone(obj) {
  return {...obj}; // this is pure.
}

function updateName(obj) {
  const obj2 = clone(obj);
  obj2.name = 'hyunsol';;
  return obj2;
}

updateName(obj);
console.log(obj); // {name: "Andrei"}
console.log(obj2); // {name: "hyunsol"}

updateName 함수는 외부 state를 복사한 데이터를 다루고 있기 때문에 원본에 변경을 가하지 않는다는 점에서 immutable하다.

댓글