함수형 프로그래밍

Minseop Jeong·2022년 2월 27일
0

함수형 프로그래밍의 중요성은 늘 강조 되고 있다. 함수형 프로그래밍의 어떤 특성, 어떤 장점 때문에 강조 되는지 알 필요가 있다. 이번 포스트에서는 함수형 프로그래밍의 정의, 특성, 기능, 장점 등에 대해 정리해보겠다.

함수형 프로그래밍의 정의

함수란?

함수형 프로그래밍에 대해 알아 보기 전에, 함수의 수학적 정의에 대해 먼저 짚고 넘어 가야 한다. 다음과 같이 함수에 대해 수학적으로 정의할 수 있다.

두 변수 x, y에 대하여 x가 정해지면 그에 따라 y가 오직 하나의 값으로 결정 될 때, y를 x의 함수라고 한다.

수학에서의 함수는 아무런 side effect가 없다. y = ax 와 같은 '함수'는 얼마나 많은 일을 수행하던 ax 라는 결과를 반환하며, 함수 내부에서는 함수 외부의 전역 상태를 변경하거나 영향을 주지 않는다.

side effect가 없다는 것은 참조 투명성(referential transparency)이 보장 된다는 말이다. 이 특성이 보장 되면 첫 번째, 함수를 아무데서나 호출 할 수 있고, 항상 같은 방식으로 수행될 것이라는 걸 알 수 있다. 두 번째, 입력에 대해 늘 결과가 같으므로 특정 x에 대한 함수 값 y를 특정값으로 치환할 수 있다.

함수는 다른 함수를 인자로 받을 수 있다. 이를 고차 함수(higher-order function)라고 한다.

함수형 프로그래밍이란?

함수형 프로그래밍은 함수의 수학적 특성을 반영하여 설계한 프로그래밍 패러다임이다. 함수형 프로그래밍은 다음과 같이 정의 된다.

함수형 프로그래밍이랑 모든 것을 side effect가 없는 함수(pure function)로 나누어 문제를 해결 하는 기법이다.

함수형 프로그래밍의 특징

함수형 프로그래밍은 다음과 같은 특징들이 존재한다.

immutable

side effect가 발생하지 않는 pure function을 사용하여 프로그래밍을 하는 패러다임이기에, 같은 입력에 대해서 항상 같은 출력이 보장된다. 또한 함수의 실행으로 인해 외부의 상태나 객체의 필드값이 변하지 않는다.

higher-order function

함수를 인자로 전달 받는 함수, 또는 함수를 리턴하는 함수를 말한다. 함수를 인자로 전달 받거나 리턴하려면 함수는 일급 객체(first class citizen)여야 한다.

def highOrderFunction(func: (Int, Int) => Int, a: Int, b: Int) = func(a, b)

def sum(a: Int, b: Int) = {a + b}
val multiply: (Int, Int) => Int = {(a, b) => a * b}

println(highOrderFunction(sum, 3, 4) // 7
println(highOrderFunction(multiply, 3, 5) // 15
println(highOrderFunction({(a, b) => a - b}, 10, 4) // 6

위 코드는 고차 함수의 사용 예제이다. 첫번째 print문 처럼 def 키워드를 사용하여 정의한 함수를 인자로 넘길 수도 있고, 두번째 print문 처럼 람다함수를 대입한 변수를 인자로 넘길 수도 있고, 세번째 print문 처럼 람다 함수를 바로 인자로 넘길 수도 있다.

여러 thread에서 안전하게 접근 가능

함수에 side effect가 없고, 함수 자체가 독립적으로 수행 가능하다. 따라서 여러 thread에서 변수에 접근 하더라도 안전하다. thread의 안전성이 보장되기에 synchronization, lock, unlock과 같은 기능 없이 병렬처리가 수행 가능하다.

재귀

모든 경우에 side effect가 없을 수는 없다. 예를 들어 입출력은 함수 외부에 환경에 따라 다른 결과를 가져올 수 있다. 또한 객체의 상태 같은 경우도 외부 적으로 변화 될 수 있다. 이런 경우는 함수형 프로그래밍에서는 상태를 항상 새로운 인스턴스나 새로운 스택 프레임으로 표현한다. 아래 예제들은 factorial을 계산하기 위한 scala 코드 예제들이다.

def factorial(i: Int): Long = {
  var result = 1
  
  for (x <- 1 to i)
    result = result * x

   return result
}

println(factorial(5))

첫번째 코드는 순차적 루프를 사용한 구현이다. 변수 ix는 값이 변하는 변수이다.

def factorial(i: Int): Long = {
  def fact(i: Int, accumulator: Int): Long = {
    if (i <= 1) accumulator
    else fact(i-1, i*accumulator)
  }
  
  fact(i, 1)
}

println(factorial(5))

두번째 코드는 재귀를 사용한 구현이다. 이 구현에서는 값이 변하는 변수는 없다. 계산하는 과정에서는 메모리 스택 프레임내의 한 값을 변경하지 않는다. 즉 상태값이 변하는 것이 아닌, 새로운 상태값으로 표현하며 계산을 한다. 물론 이와 같이 작성하면 반복되는 함수 호출에 의한 성능 저하 및 스택오버플로우 발생 가능 이라는 단점들이 존재한다.

정리

  • 함수형 프로그래밍은 side effect가 존재하지 않는 pure function들로 프로그램을 구성하는 패러다임이다.
  • side effect가 존재하지 않는 다는 것은 변수의 값이 변경되지 않으며, 객체의 상태나 필드값 또한 변경되지 않고, I/O와 같이 외부의 영향을 받지 않으며, 예외나 오류가 발생하여 실행이 중단되지 않는 것이다.
  • 함수형 프로그래밍의 특징으로는 immutable 하며, 고차 함수를 사용할 수 있고, 여러 스레드에서 접근해도 안전하며, for loop 대신 재귀 등의 방식으로 side effect를 없앤다.
  • 함수형 프로그래밍이 만능은 아니며, 애플리케이션의 목적에 맞게 적절하게 선택해야 한다.

Reference

profile
Data Engineer

0개의 댓글