본문 바로가기

개발/javascript

함수14 - 외부 상태의 변경과 함수형 프로그래밍

"함수"

 

6. 외부 상태의 변경과 함수형 프로그래밍

원시 값은 값에 의한 전달(Pass by value), 객체는 참조에 의한 전달(Pass by reference) 방식으로 동작한다. 매개변수도 함수 몸체 내부에서 변수와 동일하게 취급되므로 매개변수 또한 타입에 따라 값에 의한 전달, 참조에 의한 전달 방식을 그대로 따른다.

 

함수의 매개변수에 값을 전달하는 방식을 값에 의한 호출(Call by value), 참조에 의한 호출(Call by reference)로 구별해 부르는 경우도 있으나 동작 방식은 값에 의한 전달, 참조에 의한 전달과 동일하다. 아래 예제를 살펴보자.

// 매개변수 primitive는 원시값을 전달받고, 매개변수 obj는 객체를 전달받는다.
function changeVal(primitive, obj) {
	primitive += 100;
	obj.name = 'Kim';
}

// 외부 상태
var num = 100;
var person = { name: 'Lee' };

console.log(num)	// 100
console.log(person)	// {name: "Lee"}

// 원시값은 값 자체가 복사되어 전달되고 객체는 참조값이 복사되어 전달된다.
changeVal(num, person);

// 원시 값은 원본이 훼손되지 않는다.
console.log(num);	// 100

// 객체는 원본이 훼손된다.
console.log(person);	// {name: "Kim"}

changeVal 함수는 매개젼수를 통해 전달 받은 원시 타입 인수와 객체 타입 인수를 함수 몸체에서 변경한다. 보다 엄밀히 말하자면 원시 타입 인수를 전달받은 매개변수 primitive의 경우, 원시값은 변경 불가능한 값(immitable value)이므로 직접 변경할 수 없기 때문에 재할당을 통해 할당된 원시값을 새로운 원시값으로 교체하였고, 객체 타입 인수를 전달받은 매개변수 obj의 경우, 객체는 변경 가능한 값(mutable vlaue)이므로 직접 변경할 수 없기 때문에 재할당을 통해 할당된 원시값을 새로운 원시값으로 교체하였고, 객체 타입 인수를 전달받은 매개변수 obj의 경우, 객체는 변경 가능한 값(mutable value)이므로 직접 변경할 수 있기 때문에 재할당 없이 직접 할당된 객체를 변경하였다.

 

이때 원시 티입 인수는 값 자체가 복사되어 매개변수에 전달되기 때문에 함수 몸체에서 그 값을 변경(재할당을 통한 교체)하여도 원본은 훼손되지 않는다. 다시 말해 외부 상태, 즉 함수 외부에서 함수 내부로 전달한 원시값의 원본을 변경하려는 어떠한 부수 효과(side-effect)도 발생하지 않는다.

 

하지만 객체 타입 인수는 참조값이 복사되어 매개변수에 전달되기 때문에 함수 몸체에서 참조값을 통해 참조한 객체를 변경할 경우 원본이 훼손된다. 다시 말해 외부 상태, 즉 함수 외부에서 함수 몸체 내부로 전달한 참조 값에 의해 원본 객체가 변경되는 부수 효과가 발생한다.

 

이처럼 외부 상태(위 예제의 경우, 객체를 할당한 person 변수)를 변경시키는 것은 코드의 복잡성을 증가시키고 가독성을 해치는 원인이 된다. 함수 내부의 동작을 유심히 관찰하지 않으면 외부 상태가 변하는지 아닌지 알기 어렵기 때문이다. 언제나 그러하듯 논리가 간단해야 버그가 숨어들지 못한다.

 

이러한 현상은 객체가 변경할 수 있는 값이며 참조에 의한 전달 방식으로 동작하기 때문에 발생하는 부작용이다. 여러 변수가 참조에 의한 전달 방식을 통해 참조값을 공유하고 있다면 이 변수들은 언제든지 참조하고 있는 객체를 직접 변경할 수 있다. 복잡한 코드에서 의도치 않은 객체의 변경을 추적하는 것은 어려운 일이다. 객체의 변경을 추적하려면 Observer 패턴등을 통해 객체를 참조하고 공유하는 모든 이들에게 변경 사실을 통지하고 이에 대처하는 추가 대응이 필요하다.

 

이러한 문제의 해결 방법 중 하나는 객체를 불변 객체(immutable object)로 만들어 사용하는 것이다. 객체의 복사본을 새롭게 생성하는 비용은 들지만 객체를 마치 원시 값처럼 변경 불가능한 값으로 동작하게 만드는 것이다. 이를 통해 객체의 상태 변경을 원천 봉쇄하고 객체의 상태 변경이 필요한 경우에는 참조가 아닌 객체의 방어적 복사를 통해 원본 객체를 완전히 복제, 즉 깊은 복사(Deep copy)를 통해 새로운 객체를 생성하여 재할당을 통해 교체한다. 이를 통해 외부 상태가 변경되는 부수 효과를 없앨 수 있다.

 

함수형 프로그래밍에서는 어떤 외부 상태도 변경시키지 않는, 즉 부수 효과가 없는 함수를 순수 함수(Pure function), 외부 상태를 변경시키는 즉, 부수 효과가 있는 함수를 비순수 함수(Impure function)라고 부른다. 위 예제의 changeVal 함수와 같은 비순수 함수는 코드의 복잡성을 증가시킨다. 비순수 함수를 최대한 줄이는 것은 부수 효과를 최대한 억제하는 것과 갇타.

 

함수형 프로그래밍은 변수의 사용을 억제하여 상태 변경을 최대한 억제하고 순수 함수와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡송을 해결하려는 프로그래밍 패러다임이다. 변수 값은 누군가에 의해 언제든지 변경될 수 있고, 조건문이나 반복문은 로직의 흐름을 이해하기 어렵게 만들어 가독성을 해치고 오류 발생의 근본적 원인이 될 수 있기 때문이다.

 

함수형 프로그래밍은 결국 순수 함수를 통해 부수 효과(Side effect)를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이려는 노력의 한 방법이라고 할 수 있다.

'개발 > javascript' 카테고리의 다른 글

함수16 - 재귀 함수  (1) 2023.10.04
함수15 - 즉시 실행 함수  (1) 2023.10.04
함수13 - 반환문  (0) 2023.10.04
함수12 - 매개변수의 최대 개수  (0) 2023.10.04
함수11 - 인수 확인  (0) 2023.10.04