본문 바로가기

개발공부/기술면접준비

[JavaScript] 순수함수의 불변성 및 사이드 이펙트

순수함수란?

순수함수는 함수형 프로그래밍의 기본 개념으로 동일한 입력이 주어지면 항상 동일한 출력을 생성하고 사이드 이펙트(부작용)가 없는 함수를 나타낸다.
함수형 프로그래밍에서는 조합성을 강조하는데, 이것은 모듈화(Modularity) 수준을 높이자는 것이다.

순수함수의 특징

  • 결정적(Deterministic) : 순수함수는 같은 입력값을 주면 항상 같은 출력값을 반환한다.
    이 특성은 코드를 예측가능하고 신뢰성 있게 만든다. 동일한 인수로 함수를 호출하면 매번 동일한 결과를 얻을 수 있기 때문이다.
  • 사이드 이펙트 없음 : 순수함수는 사이드 이펙트가 없어야한다.
    사이드 이펙트란, 함수가 자신의 스코프 외부의 환경을 변경하는 것을 의미한다.
    일반적인 예시로는 전역 변수 수정, 파일 작성하기, 네트워크 요청, 인수로 전달된 객체의 상태 변경 등이 있다.
    순수함수는 모든 변경사항이 함수의 로컬 스코프로 제한되며 프로그램 상태나 외부 시스템에 영향을 미치지 않는다.
  • 참조 투명성(Referential Transperency) : 순수함수는 사이드 이펙트가 없고 결정적이기 때문에 참조 투명성을 가진다.
    이것은 함수 호출을 그 결과값으로 대체해도 프로그램 동작이 변경되지 않는다는것을 의미한다.
    이러한 특성은 코드를 이해하기 쉽게 만들고 메모이제이션(Memoization)과 같은 최적화를 가능하게 한다.
  • 불변 데이터(immutable Data) : 순수함수는 불변 데이터 구조와 잘 어울린다.
    불변 데이터 구조는 생성된 후 수정할 수 없다. 데이터를 직접 변경하는 대신 원하는 변경 사항을 반영한 새로운 데이터 구조를 생성한다.
    순수 함수는 데이터를 직접 수정하지 않고 입력에 기반하여 새로운 데이터를 생성하므로 불변 데이터와 잘 맞는다.
  • 텍스트의 용이성 : 순수 함수는 사이드이펙트나 외부 상태에 의존하지 않으므로 테스트하기가 용이하다.
    특정 입력을 제공하고 출력을 확인함으로써 함수를 독립적으로 테스트할 수 잇으므로 단위 테스트(Unit Testing)가 간편해진다.
  • 조립 가능성(Composability) : 순수함수는 조립이 가능하다. 순수함수를 결합하여 더 복잡한 함수나 시스템을 구축할 수 있으며 사이드 이펙트가 없기 때문에 예기치 않은 동작을 도입하지 않고 안정적으로 조합할 수 있다.

함수형 프로그래밍(Functional Programming, FP)이란?

함수형 프로그래밍은 프로그래밍 패러다임 중 하나로, 계산을 수학적 함수의 평가로 간주하고 이를 기반으로 소프트 웨어를 개발하는 방법론이다.

함수형 프로그래밍의 특징에 대해서 살펴보자

  • 함수의 일급 객체(First-Class Functions) : 함수형 프로그래밍에서는 함수가 변수에 할당되거나 다른 함수의 인수로 전달될 수 있으며, 함수에서 다른 함수를 반환할 수 있다.
    이것을 통해 함수를 데이터처럼 다룰 수 있으며, 고차 함수(Higher-Order Functions)를 활용하여 코드를 추상화하고 모듈화할 수 있다.
  • 불변성(Immutability) : 함수형 프로그래밍에서는 데이터를 불변하게 취급한다.
    한번 생성된 데이터는 수정할 수 없으며, 대신에 변경이 필요한 경우 새로운 데이터를 생성한다.
    이것은 사이드 이펙트를 방지하고 코드의 예측가능성과 안정성을 높인다.
  • 순수함수(Pure Functions) : 순수함수는 동일한 입력에 대해 항상 동일한 출력을 반환하며 사이드 이펙트가 없는 함수를 말한다. 이러한 함수는 예측 가능하고 테스트하기 쉽다.
  • 불변성과 순수함수를 통한 상태관리(State Management) : 함수형 프로그래밍에서는 상태를 변경하는 대신 새로운 상태를 생성하고 이전 상태를 유지한다.
    이것을 통해 복잡한 상태관리 문제를 해결하고 동시성을 다루기 쉽게 만든다.
  • 재귀(Recursion) : 함수형 프로그래밍은 재귀를 활용하여 반복적인 작업을 수행한다.
    루프 대신 재귀함수를 사용하여 코드를 간결하게 작성할 수 있다.
  • 데이터 변환과 조합(Transformation and Composition) : 함수형 프로그래밍은 데이터 변환과 조합에 중점을 둔다.
    함수 체인을 사용하여 데이터를 변환하고 조합하며, 이를 통해 복잡한 데이터 처리를 단순화한다.
  • 람다 계산법(Lambda Calculus) : 함수형 프로그래밍의 이론적 기반 중 하나로, 함수의 적용과 추상화에 관한 형식적인 모델을 제공한다.

함수형 프로그래밍은 병렬처리와 분산 시스템에서의 장점을 가지며, 코드의 가독성과 유지보수성을 향상시키는데 도움을 준다.
이러한 이점으로 함수형 프로그래밍은 모던 소프트웨어 개발에서 널리 사용되고 있으며, 여러 프로그래밍 언어에서 함수형 프로그래밍의 개념과 패턴을 지원하고 있다.

함수형 프로그래밍에서 조합성(Composition)과 모듈화(Modularity)란?

함수형 프로그래밍에서 조합성과 모듈화는 중요한 개념이다.
이러한 개념은 코드의 재사용성과 유지 보수성을 향상시키며, 프로그램을 더 효과적으로 작성하는데 도움을 준다.

  • 조합성 : 조합성은 함수를 조합하여 더 복잡한 동작을 만들어내는 능력을 가리킨다.
    함수형 프로그래밍에서 함수는 일급객체이므로 다른 함수의 인수로 전달하거나 반환할 수 있다.
    이것을 통해 함수를 조합하여 새로운 함수로 만들 수 있다.

예제 코드를 살펴보자

// 함수 조합 예제 (JavaScript)
function f(x) {
  return x * 2;
}

function g(x) {
  return x + 1;
}

// f와 g를 조합하여 h를 만듭니다.
const h = (x) => f(g(x));

console.log(h(2)); // 결과: 6 (2를 먼저 g에 적용하고, 그 결과를 f에 적용)

위 예제코드와 같이 함수 조합을 통해 코드를 간결하게 작성하고 다양한 동작을 수행할 수 있다.
(수학적 관점에서 보면 합성함수를 만드는것과 비슷하다.)

  • 모듈화 : 함수형 프로그래밍은 코드를 작은 모듈로 나누고 각 모듈을 독립적으로 테스트하고 재사용 가능하도록 설계하는데 중점을 둔다.
    모듈화는 코드를 더 이해하기 쉽게 만들고 유지 보수를 단순화한다.
    함수형 프로그래밍에서 모듈은 주로 함수로 구성되며, 각 함수는 특정 작업 또는 계산을 수행하는 역할을 한다.
    이러한 함수 모듈은 각각의 입력과 출력을 가지며, 함수 사이의 의존성을 최소화하려고 노력한다.
    모듈화의 장점 중 하나는 코드를 여러 프로젝트 또는 다른 부분에서 재사용하기 쉽게 만든다는 것이다.
    또한 작은 모듈들을 조합하여 더 큰 기능을 구현할 수 있으며, 각 모듈을 독립적으로 테스트하고 디버그할 수 있다.

함수형 프로그래밍에서 Map, Filter, Reduce와 같은 배열 조작 함수는 모듈화된 작업을 수행하고 배열을 처리하는 다른 부분에서 재사용할 수 있도록 한다.

예제코드를 살펴보자

// 배열 조작 함수 모듈화 (JavaScript)
function map(array, fn) {
  const result = [];
  for (const item of array) {
    result.push(fn(item));
  }
  return result;
}

function filter(array, fn) {
  const result = [];
  for (const item of array) {
    if (fn(item)) {
      result.push(item);
    }
  }
  return result;
}

// 모듈화된 함수들을 사용하여 배열 조작
const numbers = [1, 2, 3, 4, 5];
const doubled = map(numbers, (x) => x * 2);
const even = filter(numbers, (x) => x % 2 === 0);

이러한 모듈화된 함수들은 각각의 역할을 명확하게 정의하고 코드를 읽고 이해하기 쉽게 만든다.

조합성과 모듈화는 함수형 프로그래밍의 핵심 개념 중 하나로, 코드의 유지보수성, 확장성 및 재사용성을 향상시키는데 큰 도움을 준다.
함수형 프로그래밍은 원칙을 활용하여 더 간결하고 신뢰성있는 소프트웨어를 개발하는데 도움을 준다.

순수함수와 순수함수가 아닌 예시

순수함수의 예시

자바스크립트의 순수함수의 예시를 살펴보자

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

add(10, 5) // 15
add(10, 5) // 15
add(10, 5) // 15

add함수는 동일한 입력값(a, b)에 대해 항상 동일한 결과를 반환하고 사이드 이펙트가 없기 때문에 순수하다.
외부 변수를 수정하거나 값을 반환하는것 이상의 작업을 수행하지 않는다.

순수함수가 아닌 예시

그렇다면 순수함수가 아닌 예시에 대해서 살펴보자

var c = 10;
function add2(a, b) {
    return a + b + c;
}

add2(10, 5) // 25
add2(10, 5) // 25
add2(10, 5) // 25
c=20;
add2(10, 5) // 35
add2(10, 5) // 35
add2(10, 5) // 35

위의 함수의 경우 같은 인자를 주어도 c의 값에 따라 결과가 달라지므로 순수함수가 아니라고 할 수 있다.
또 다른 예시도 살펴보자

var obj1 = {val: 10};
function add3(obj, b) {
    obj.val += b;
}

// obj1.val = 10
add3(obj1, 10)
// obj1.val = 20

위의 함수의 경우 함수 외부에 있는 obj1이라는 객체의 값을 직접 변경한다.
이 경우, 외부에 미치는 영향이 있기 때문에 순수함수가 아니라고 할 수 있다.

그렇다면 이러한 함수형 프로그래밍에서는 객체를 어떻게 다룰까?

함수형 프로그래밍에서 객체를 다루고 싶으면 원본 객체의 값을 참조하여 같은 형태의 객체를 만들어서 리턴해주면 된다.
예제 코드를 한번 보자

var obj1 = {val: 10};
function add4(obj, b) {
    return {val: obj.val += b }
}

// obj1.val = 10
add3(obj1, 10)
// obj1.val = 10

결과적으로 obj1val값은 유지되면서 함수 외부에 미치는 영향이 없으므로 위와 같은 함수는 순수함수라고 할 수 있다.

순수함수를 쓰는 이유?

순수함수가 아닌 함수를 사용하면 외부의 값이 달라지거나 외부에 값에 의해 함수의 결과에 영향을 끼친다.
이럴 경우 함수의 평가시점이 중요해지게 되는데 개발자의 실수로 인해 예상치 못한 일이 일어나거나 혹은 알아차리지 못하는 새에 그런 일들이 벌어질 수 있다.

하지만 순수함수는 평가시점에 관계없이 동일한 인자에 결과를 돌려주며 함수와 관계없는 것들에는 영향을 받지 않기 때문에 함수 자체를 인자로 넘기거나 다른 스레드에서 실행되는 등의 여러가지 상황에서 안전성이 보장된다.
따라서 순수함수를 작성하면 개발자가 함수를 다루기 쉬워지고 개발 효율을 높일 수 있게 된다.

순수함수 관점에서 불변성이란?

순수 함수가 입력 데이터를 수정하지 않고 동일한 입력에 대해 항상 동일한 출력을 생성한다는 속성을 의미한다.
이는 일부 데이터를 순수 함수에 대한 입력으로 전달될때 함수가 어떤 방식으로든 해당 입력 데이터를 변경하거나 변경해서는 안된다는 것을 의미한다.
대신 원본 데이터를 변경하지 않고 입력 데이터만을 기반으로 새 데이터 또는 새 결과를 생성하고 반환해야 한다.

즉, 순수함수는 결정적이며 사이드 이펙트가 없다.
프로그램 상태를 변경하거나 외부 변수를 수정하지 않는다.

이러한 불변성 속성은 함수의 동작을 예측 가능하게 하고 코드에 대한 추론을 더 쉽게 만들어주기 때문에 함수형 프로그래밍에서 중요하다.
또한 공유된 변경 가능 상태에 대한 걱정 없이 순수함수를 동시에 안전하게 호출할 수 있으므로 병렬 및 동시 프로그래밍이 용이하다.

사이드 이펙트란?

프로그래밍 측면에서 사이드 이펙트는 함수나 작업을 실행한 결과로 시스템에서 발생하는 관찰 가능한 변경이나 동작을 의미한다.
사이드 이펙트는 단순히 값을 계산하고 반환하는 것 이상의 작업을 수행하는 함수나 절차와 관련된 경우가 많다.
대신 변수 값 변경, 파일 또는 데이터베이스와의 상호작용, 환경에 영향을 미치는 출력 생성 등 외부 상태돠 상호 작용하거나 수정할 수 있다.

다음은 사이드 이펙트의 몇가지 일반적인 예시들이다.

  1. 가변 상태 수정(Modifying Mutable State) : 함수가 로컬 범위 외부의 변수 값을 수정하는 경우 사이드 이펙트가 있는 것으로 간주된다.

예제코드를 살펴보자

count = 0

def increment_count():
    global count
    count += 1

위 코드와 같이 전역 변수를 증가시키는 함수에는 사이드 이펙트가 존재한다.

  1. I/O 작업(I/O Operations) : 파일, 데이터베이스, 네트워크 소켓, 콘솔에서 읽고 쓰는 기능은 외부 리소스의 상태를 변경할 수 잇기 때문에 사이드이펙트가 잇다.
def write_to_file(data):
    with open('file.txt', 'w') as file:
        file.write(data)

위는 I/O작업과 관련된 예제코드이다.

  1. 출력 : 콘솔에 출력하거나 사용자에게 정보를 표시하는 기능도 외부 환경에 영향을 미치기 때문에 사이드 이펙트를 낳는다.
    예제 코드를 살펴보자
def greet(name):   
    print(f"Hello, {name}!")

 

  1. Exception Throwing : 함수의 코드의 다른 부분에서 포착할 수 있는 예외를 발생시키는 경우 제어 흐름에 영향을 주기 때문에 사이드 이펙트가 있는 것으로 간주된다.

예제 코드를 살펴보자

def divide(a, b):
    if b == 0:
        raise ValueError("Division by zero")
    return a / b

사이드 이펙트가 없고 동일한 입력에 대해 동일한 출력을 생성하는 순수함수와는 달리, 사이드 이펙트가 있는 함수는 예상치못한 동작으로 이어질 수 있으며, 프로그램의 동작을 추론하기 어렵게 만든다.
함수형 프로그래밍은 종종 사이드 이펙트를 최소화하고 필요할 때 이를 분리하도록 권장한다.
이것을 통해 코드를 더 쉽게 테스트하고 디버깅하고 유지관리할 수 있다.

정리하자면, 사이드 이펙트는 기능이 실행될 때 발생하는 관찰 가능한 변화 또는 상호작용이며 소프트웨어 시스템의 동작을 설계하고 이해할 때 중요한 고려사항이다.
함수형 프로그래밍은 부작용을 줄이거나 관리하여 코드 신뢰성과 유지 관리성을 향상시키는 방법을 모색한다.

순수함수 관점에서 사이드 이펙트란?

  • 순수함수의 사이드 이펙트 : 순수함수가 사이드 이펙트를 일으킨다는것은 일어나서는 안되는 일이 일어나고 있다는 뜻이다.
    예를 들어 전역 변수를 수정하거나, 화면에 출력하거나, 인터넷을 통해 데이터를 전송하는 것들을 말한다.
    이것은 순수함수가 외부 세계와 독립적이고 예측 가능한 것으로 여겨지기 때문에 문제가 된다.
  • 순수함수에서 사이드 이펙트를 피해야 하는 이유 : 순수함수를 사용하면 동일한 입력을 제공하면 항상 동일한 출력을 얻을 수 있다는 것을 우리는 알고 있다.
    하지만 사이드 이펙트로 인해 무작위성 또는 예측 불가능성이 발생할 수 있다.
    또, 순수함수는 주어진 입력에 대한 예상 출력을 반환하는지만 확인하면 되기 때문에 테스트가 용이해진다. 사이드 이펙트가 잇으면 테스트가 복잡해진다.
    마지막으로 문제를 찾기 위해 함수 내부만 확인하면 되기 때문에 순수함수에서는 디버깅이 더 간단해진다.
    사이드 이펙트는 어디에서나 발생할 수 있으므로 버그를 추적하기가 더 어렵다.

 

 불변성과 사이드 이펙트와 관련하여 이것이 무엇을 의미하는지 정리해보자.

  • 결정론적(Deterministic) : 순수함수는 결정론적이어야 한다.
    즉, 주어진 입력 값 세트에 대해 항상 동일한 출력을 생성한다는 의미이다.
    이 속성은 순수 함수를 예측 가능하고 안정적으로 만드는데, 이것은 함수형 프로그래밍에서 매우 중요하다
  • 사이드 이펙트 없음 : 순수함수에는 사이드이펙트가 없다.
    사이드이펙트는 함수가 해당 범위 외부의 환경에 적용하는 모든 변경사항이다.
    사이드이펙트의 일반적인 예시로는 전역 변수 수정, 파일 작성하기, 네트워크 요청, 인수로 전달된 개체 상태 변경 등이 있다.
    순수함수에서는 모든 변경사항이 함수의 로컬 범위로 제한되며 프로그램이나 외부시스템의 상태에 영향을 주지 않는다.
  • 불변성 : 순수함수는 불변 데이터 구조와 잘 작동하는 경우가 많다.
    불변 데이터 구조는 생성된 후 에는 수정할 수 없다. 데이터를 제자리에서 변경하는 대신 원하는 변경 사항으로 새로운데이터 구조가 생성된다.
    순수 함수는 데이터를 직접 수정하지 않기 때문에 불변 데이터 작업에 적합하다.
    입력을 기반으로 새로운 데이터를 생성하고 반환한다.

정리하자면, 순수함수는 입력에만 기반되며 일관된 결과를 생성하고 사이드 이펙트가 없는 함수이다.
불변성은 데이터가 변경되지 않은 상태로 유지되고 수정하지 않고도 기존 데이터를 기반으로 새 데이터를 생성하는데 도움이 되기 때문에 종종 순수 기능과 함께 사용된다.
이 함수형 프로그래밍 패러다임은 깔끔하고 예측 가능한 코드를 장려하여 추론과 테스트를 더 쉽게 만든다.

참고자료 : 블로그-함수형프로그래밍 순수함수