본문 바로가기
👩🏻‍💻 Computer Science

순수함수

by iirin 2023. 2. 6.

순수함수

일반적으로 이야기하는 프로그래밍에서 순수 함수의 특징들은 수학적 함수의 성질에서 기원한다. 수학적 함수의 정의에서 정의역 1개의 값은 2개 이상의 치역을 가질 수 없다.

프로그래밍에서 순수함수는 다음과 같은 특징을 갖는다.

  • 함수의 인자로 사용되는 값 하나에 대응하는 함수의 결과값이 유일하게 존재한다.
  • 평가 시점이 중요하지 않다.
    • 언제 어디서든 동일한 인자값에는 동일한 값이 반환된다.
  • 불변성 : 함수 외부의 상태를 변경하지 않는다. 이를 부수효과side effect 가 없다 라고도 한다.
  • 참조투명성 : 외부 상태를 변경하지도 않고 외부의 상태로부터 영향 받지 않는다.

 

부수효과 side effect 란?

외부에서 받는 효과로 다음과 같은 예시 상황들이 있다.

  • 자료구조를 고치거나 필드값에 할당
  • 자료구조를 제자리에서 수정
  • 예외 발생, 오류로 실행 중단
  • 파일 읽기/쓰기 등의 I/O 동작 수행 등...

 

순수함수의 예시

// 순수함수의 예
fun main(args: Array<String>) {
	println(pureFunction("FP")  // Hello FP
	println(pureFunction("FP")  // Hello FP
	println(pureFunction("FP")  // Hello FP
}

fun pureFunction (iniput: String): String {
	return "Hello " + input
}
  • 몇 번을 호출하든 인자가 같으면 출력값도 같다.
// 비 순수함수의 예1
fun main(args: Array<String>) {
	println(nonPureFunction("FP")  // Hello FP
	println(nonPureFunction("FP")  // Hello Hello FP
	println(nonPureFunction("FP")  // Hello Hello Hello FP
}

val strBuilder: StringBuilder = StringBuilder("Hello ")

fun nonPureFunction(input: String): String {
	return strBuilder.append(input).toString()
}
  • 출력값이 매번 다르다.
  • 동시성을 생각했을 때 (멀티스레드) 굉장히 프로그래밍이 복잡해진다.
    • 디버깅, 에러 확인이 힘들다.
  • 테스트 코드 작성이 힘들다.
// 비 순수함수의 예2
var x =1

fun nonPureFunction2(input: Int): Int{
	return input+x // 외부 변수 참조
}
  • 외부 변수를 참조하고 있다.
    • 외부 변수가 언제 바뀔지 모르므로 값이 변경될 가능성이 있다.

 

순수함수인 것이 중요해?

  • 순수함수가 아닌 것이 나쁜 코드라는 것은 아니라 단지 함수형 프로그래밍이라는 패러다임일 뿐이다.
  • 모듈화 수준이 높으면 재사용성이 높아진다.
  • 궁극적으로 평가 시점이 무관하다는 특성으로 효율적인 로직을 구성하는 것이 함수형 프로그래밍의 패러다임이다.
  • Side effect가 없으므로 병렬처리 (Thread)의 안정성을 확보할 수 있다. mutex 동기화를 고민하지 않아도 된다는 장점이 있다. Mutex 이해를 위한 참조링크

불변성

순수 함수와 불변성의 관계

  • 수학 세계에서는 '무언가를 저장하고 변경하고 불러온다'라는 개념이 없기 때문에 함수는 함수 자체로 독립적으로 존재한다.
  • 프로그래밍을 하며 불변성을 유지하기 위해서 꽤나 신경써 주어야 한다. 그래서 몇 가지 규칙들로 이를 유지하고자 한다.
    • 프로그램에서 변이Mutation이 발생하는 근본 원인을 파악하고 대응해보자.

 

불변성과 상태

  • 상태를 변경하지 않는 것.
  • 단순히 프로그램 변수를 변경하거나 재할당 하는 것을 의미하는 것만이 아니라 메모리에 저장된 값을 변경하는 모든 행위를 의미한다.
    • 값에 의한 호출과 참조에 의한 호출
    • 불변성을 지키고 싶다면 메모리 상태를 항상 염두에 두고 코드를 작성해야 한다.

 

불변성을 지키는 것의 장점

이렇게 신경쓸 것들이 많은데, 왜 불변성을 지키고자 할까?

  1. 무분별한 상태의 변경을 막을 수 있다.
    • 무분별한 상태 변경의 대표적인 예 : 전역 변수의 남용
  2. 상태의 변경을 추적하기 쉽다.

참조투명성

  • 모든 프로그램에 대해 어떤 표현식expression e를 모두 그 표현식의 결과로 치환해도 프로그램에 아무 영향이 없다면 그 표현식 e는 참조에 투명하다(referentially transparent)고 할 수 있다.
  • == 함수 외부의 영향을 받지 않는 것.
    • 부수 효과를 없애는 것(외부 세계를 변경하지 않는 것)에 더해서 외부로부터 영향받지 않아야 한다.
    • 함수의 결과는 입력 파라미터에만 의존하고 함수 외부 세계인 입력 콘솔, 파일, 원격, URL, 데이터베이스, 파일시스템 등에서 데이터를 읽지 않는다.

 

참조투명성 특징

  • 자기 충족적 self-contained
    • 함수 외부에 의존하는 코드가 없고, 함수 사용자 입장에서 유효한 매개변수만 전달하면된다.
  • 결정론적 deterministic
    • 동일한 매개변수에 대해 항상 동일한 결과가 나온다.
  • 예외 Exception 을 던지지 않는다.
    • 에러들은 버그로 취급한다.
  • 다른 코드가 예기치않게 실패하는 조건을 만들지 않는다.
    • 매개 변수의 값을 변경하거나 함수 외부 데이터를 변경하지 않는다.
  • 데이터베이스, 파일 시스템, 네트워크 등의 외부 기기로 인해 동작이 멈추지 않는다.

 

ref.