함수형 프로그래밍이란?

de_sj_awa·2021년 5월 17일
1

선언형 프로그래밍 : 어떻게 할 것이지(How)를 나타내기보다 무엇(What)을 할 것인지를 설명하는 방식
- 함수형 프로그래밍 : 순수 함수를 조합하고 소프트웨어를 만드는 방식(클로저, 하스켈, 리스프)

1. 함수형 프로그래밍(Functional Programming)의 등장

명령형 프로그래밍을 기반으로 개발햇던 개발자들은 개발하는 소프트웨어의 크기가 커짐에 따라, 복잡하게 엉켜있는 스파게티 코드를 유지보수하는 것이 매우 힘들다는 것을 깨닫게 되었다. 그리고 이를 해결하기 위해 함수형 프로그래밍이라는 프로그래밍 패러다임에 관심을 갖게 되었다. 함수형 프로그래밍은 거의 모든 것을 순수 함수로 나누어 문제를 해결하는 기법으로, 작은 문제를 해결하기 위한 함수를 작성하여 가독성을 높이고 유지보수를 용이하게 해준다.

유명한 책인 클린 코드(Clean Code)의 저자 Rober C.Martin은 함수형 프로그래밍을 대입문이 없는 프로그래밍이라고 정의했다.

Functional Programming is programming without assignment satements

그동한 명령형 프로그래밍으로 개발을 해왔던 사람들에게 대입문이 없는 프로그래밍은 상당히 생소할 수 밖에 없다. 왜냐하면 다음과 같이 간단한 코드에서도 변수가 할당되고, 값이 대입되기 때문이다.

// 1~ 10까지의 값이 i에 할당된다.
for(int i = 1; i < 10; i++){
    System.out.println(i);
}

2. 함수형 프로그래밍(Functional Programming)에 대한 이해

앞서 설명하였듯 함수형 프로그래밍은 대입문을 사용하지 않는 프로그래밍이며, 작은 문제를 해결하기 위한 함수를 작성한다고 설명했다. 그렇기 때문에 함수형 프로그래밍에서는 위와 같은 코드를 아래와 같이 해결할 수 있다.

process(10, print(num))

(위의 코드는 실제 동작 여부와 무관하게 함수형 프로그래밍에 대한 이해를 돕기 위해 작성된 수도 코드이다.)

process 함수는 첫 번째 인자로 몇 까지 iteration을 돌 것인가를 매개변수로
받고 있고, 두 번재 인자로 전달받은 값을 출력하라는 함수를 매개 변수로 받고 있다.

앞서 설명하였듯 함수형 프로그래밍은 무엇을(What)에 포커스를 두는 프로그래밍이라고 하였다. 그렇기 때문에 함수형 프로그래밍에서는 "출력"을 하는 함수를 파라미터로 넘길 수 있으며 이는 함수형 프로그래밍의 기본 원래 중 함수를 1급 시민(First-Class Citizen) 또는 1급 객체(First-Class Object)로 관리하는 특징 때문이다.

명령형 프로그래밍에서는 메소드를 호출하면 상황에 따라 내부의 값이 바뀔 수 있다. 즉, 우리가 개발한 함수 내에서 선언된 변수의 메모리에 할당한 값이 바뀌는 등의 변화가 발생할 수 있다.

하지만 함수형 프로그래밍에서는 대입문이 없기 때문에 메모리에 한 번 할당된 값은 새로운 값으로 변할 수 없다.

3. 함수형 프로그래밍(Funtional Programming)의 개념

함수형 프로그래밍의 특징을 한 줄로 요약하면 다음과 같다.

부수 효과가 없는 순수 함수를 1급 객체로 간주하여 파라미터로 넘기거나 반환값으로 사용할 수 있으며, 참조 투명성을 지킬 수 있다.

여기서 부수 효과(Side Effect)란 다음과 같은 변화 또는 변화가 발생하는 작업을 의미한다.

  • 변수의 값이 변경됨
  • 자료구조를 제자리에서 수정함
  • 객체의 필드값을 설정함
  • 예외나 오류가 발생하여 실행이 중단됨
  • 콘솔 또는 파일 I/O가 발생함

그리고이러한 부수 효과(Side Effect)들을 제거한 함수들을 순수 함수(Pure Function)이라고 부르며, 함수형 프로그래밍에서 사용하는 함수는 이러한 순수 함수들이다.

  • Memory or I/O의 관점에서 Side Effect가 없는 함수
  • 함수의 입력이 주어지면, 항상 일관된 출력이 나오는 함수이다.
  • 함수의 실행이 외부에 영향을 끼지지 않고, 받지도 않는다.

순수 함수(Pure Function)을 이용하면 얻을 수 있는 효과는 다음과 같다.

  • 함수 자체가 독립적이며 Side-Effect가 없기 때문에 Thread에 안전성을 보장받을 수 있다.
  • Thread에 안전성을 보장받아 병렬 처리를 동기화 없이 진행할 수 있다.

그리고 1급 객체란 다음과 같은 것들이 가능한 객체를 의미한다.

  • 변수나 데이터 구조 안에 담을 수 있다.
  • 파라미터로 전달할 수 있다.
  • 반환값으로 사용할 수 있다.
  • 할당의 대상이 될 수 있다.
  • 비교 연산(==, equals)을 적용할 수 있다.
  • 할당에 사용된 이름과 무관하게 고유한 구별이 가능하다.

함수형 프로그래밍에서는 함수는 1급 객체로 취급받기 때문에 위에서 본 것처럼 함수를 파라미터로 넘기는 등의 작업이 가능한 것이다. 또한 우리가 일반적으로 알고 개발했던 함수들은 함수형 프로그래밍에서 정의했던 순수 함수들과는 다르다는 것을 인지해야 한다.

마지막으로 참조 투명성(Referential Transparency)이란 다음과 같다.

  • 동일한 인자에 대해 항상 동일한 결과를 반환해야 한다.
  • 참조 투명성을 통해 기존의 값은 변경되지 않고 유지된다.(Immutable Data)

명령형 프로그래밍과 함수형 프로그래밍에서 사용하는 함수는 부수효과의 유/무에 따라 차이가 있다. 그에 따라 함수가 참조에 투명한지 안한지 나뉘어지는데, 참조에 투명하다는 것은 말 그대로 함수를 실행하여도 어떤 상태의 변화 없이 항상 동일한 결과를 반환하여 항상 동일하게(투명하게) 실행 결과를 참조(예측)할 수 있다는 것을 의미한다.

즉, 어떤 함수 f에 어떤 인자 x를 넣고 f를 실행하게 되면, f는 입력된 인자에만 의존하므로 항상 f(x)라는 동일한 결과를 얻는다는 것을 의미한다. 부작용을 제거하여 프로그램의 동작을 이해하고 예측을 용이하게 하는 것은 함수형 프로그래밍으로 개발하려는 핵심 동기 중 하나이다. 그리고 이러한 부분인 병렬 처리 환경에서 개발할 때 Race Condition에 대한 비용을 줄여준다. 왜냐하면 함수형 프로그래밍에서는 값의 대입이 없이 항상 동일한 실행에 대해 동일한 결과를 반환하기 때문이다.

이를 기반으로 한 순수 함수 프로그래밍의 규칙은 다음과 같다.

  1. 데이터를 변경 불가능하게 유지한다.
  2. 함수를 순수 함수로 만든다. 인자를 적어도 하나 이상 받게 만들고, 데이터나 다른 함수를 반환해야 한다.
  3. (가능하면) 루프보다는 재귀를 사용한다.

4. 함수형 프로그래밍(Functional Programming)의 특징

  1. 불변성(Immutablity)

함수형 프로그래밍에서는 데이터의 불변성을 지킨다. 즉, 변경 가능한 상태를 최대한 제거하려고 노력한다. 데이터 구조의 복사본을 만들고 그 중 일부를 변경하는 식으로 원본 데이터를 보호할 수 있다. 순수 함수는 내부 상태를 갖지 않아, 같은 입력에 대해서는 항상 같은 출력이 보장되는 함수이다. 다르게 표현하면 부작용(side effect)가 없는 함수이다. 수학에서의 삼각 함수가 순수 함수의 한 예이다. 함수 내부에 상태가 존재하지 않으며, 함수의 출력값은 항상 함수의 입력 값만 영향을 받기 때문이다. 함수형 프로그래밍 언어가 불변성을 추구함에 따라 여러 가지 장점을 제공한다.

프로그래밍의 검증이 쉽다.

불변성은 프로그래밍의 검증을 수월하게 한다. 프로그램을 구성하는 모듈들이 오로지 입력 값의 영향만 받기 때문에 테스크 코드를 작성하기 쉽고, 프로그래머가 예측하지 못하는 시점에 변경될 수 있는 내부 상태가 없기 때문에 프로그램이 예측 가능해지기 때문이다.

최적화가 가능하다.

불변성은 다양한 최적화를 가능하게 한다. 이전에 계산한 함수의 값을 캐싱(caching)해 두었다가 필요할 때 다시 사용하는 메모이제이션(memoization)은 함수의 불변성이 보장되지 않으면 불가능하다. 호출할 때마다 결과 값이 달라지는 함수는 캐싱을 하는 것이 의미가 없기 때문이다. 불변성은 JVM이나 CLR같은 런타임 환경이 코드의 순서를 임의로 재배치할 수 있는 근거가 된다. 결과가 함수의 호출 순서와는 관계 없이 오로지 함수의 입력값에만 의존하기 때문에, 필요한 경우 런타임이 자발적으로 코드의 실행 순서를 변경하여 성능을 향상시킬 수 있다.

동시성 프로그램을 작성하기 쉽다.

멀티프로세서 환경에서 동작하는 동시성 프로그램을 작성할 때 함수형 프로그래밍 언어가 유용하다. 동시성 프로그램을 작성하기 힘든 이유는 여러 쓰레드(Thread)들이 프로그램 상태를 공유하기 때문이다. 함수형 프로그래밍에서는 변경 가능한 상태를 원천적으로 배제하기 때문에 프로그래머는 잠금(Lock)과 동기화(Synchronize)와 같은 골치 아픈 쓰레드 관련 문제에서 벗어나 핵심 로직 구현에 집중할 수 있다.

  1. 고차함수(High order function, HOF)

고차함수란 다른 함수를 조작할 수 있는 함수이다. 다른 함수를 인자로 받거나 반환할 수 있고, 때로는 그 두가지를 모두 수행한다.

  1. 커링(Currying)

커링은 고차함수 사용법과 관련한 함수형 프로그래밍 기법이다. 어떤 연산을 수행할 때 필요한 값 중 일부를 저장하고, 나중에 나머지 값을 전달 받는 기법이다. 이를 위해 다른 함수를 반환하는 함수를 사용하며, 이를 커링된 함수라고 부른다.

  1. 재귀(recursion)

재귀란 자기 자신을 호출하는 함수를 만드는 기법이다. 루프를 모두 재귀로 바꿀 수 있고, 일부 루프는 재귀로 표현하는 쪽이 더 쉽다.

  1. 합성(composition)

함수형 프로그래밍은 로직을 구체적인 작업을 담당하는 여러 작은 순수 함수로 나눈다. 그 과정에서 언젠가는 모두 작은 함수를 한데 합칠 필요가 있다. 각 함수를 서로 연쇄적으로, 또는 병렬로 호출하거나 여러 작은 함수를 조합해서 더 큰 함수로 만드는 과정을 반복해서 전체 애플리케이션을 구축한다. 합성의 목표는 '단순한 함수를 조합해 고차함수를 만들어내는 것'이다.

체이닝 : 함수를 더 큰 함수로 조합해줌(고차함수). 함수를 인자로 받아서 값을 하나 반환.

  1. Lazy evaluation

함수형 프로그래밍 언어는 지연 연산(lazy evaluation)을 지원한다. 지연 연산이란 어떤 값이 실제로 쓰이기 전까지 그 값의 계산을 최대한 미루는 것이다. 값을 미리 계산하여 저장하지 않기 때문에 공간을 절약할 수 있고, 값이 꼭 필요할 때만 계산하기 때문에 프로그래밍의 성능에도 긍정적인 영향을 준다. 지연 연산을 이용하면 무한수열을 표현할 수 있는 장점도 있다. 수열의 모든 값을 메모리에 저장하는 것이 아니라 수열의 표현을 선언하고 값이 필요할 때 계산하여 원하는 값을 생성하기 때문이다. 지연 연산은 주로 메모이제이션과 함께 사용된다. 값이 필요할 때 계산을 수행한 후, 다음에 해당 값이 필요할 때는 계산하지 않고 캐싱해 놓았던 값을 재사용하는 것이다. 지연 연산의 장점을 잘 표현하는 예제로는 피보나치 수열(Fibonacci sequence)이 대표적이다.

참고

profile
이것저것 관심많은 개발자.

0개의 댓글