엄범

 

함수형 프로그래밍이란?

계산이나 데이터 처리 등을 함수들의 조합으로 생각하는 프로그래밍 방식
  • 이 때의 함수들은 순수 함수여서, 언제 호출하든 어떤 input x에 대해 항상 같은 output y를 반환해야 함
    • 이러한 함수들이 바로 `` map, filter, reduce`` 등
  • 객체지향 프로그래밍에서는 계산이나 데이터 처리를 객체들을 조작하여 처리
    • 객체들을 조작한다는 것은 객체의 상태를 바꾼다는 것
    • 객체의 상태에 따라 어떤 input x에 대해 output이 y일 수도, y'일 수도 있음
    • 이러한 점이 객체지향과 함수형의 차이점
 
함수형 프로그래밍과 명령형(Imperative) 프로그래밍을 같이 사용할 수 있는가?
  • 함수형을 너무 엄격하게 적용하는 경우, 언어 자체가 강력하게 함수형을 지원하지 않으면 이렇게 코딩하기가 불가능하다.  
    • 예를 들어 모든 함수를 순수 함수로 작성해라? 자바에서는 불가능하다.
      • JAVA8 부터 StreamAPI를 지원한다지만, 애초에 객체 지향 자체를 염두에 두고 설계되어 나온 언어라서 객체들의 조합으로 코딩할 수 밖에 없다.
      • 객체를 쓴다는 말은, 객체는 상태를 가진다는 것이 본질이다. 어떤 메서드가 객체의 상태(필드)를 변경하도록 코딩하게 되므로 side-effect가 발생할 수 밖에 없다.  
      • 물론 불변 객체들을 이용해서 코딩할 수도 있지만, 애초에 그렇게 설계되어 있는 언어는 아니다.
  • 그럼 자바에서는 함수형 프로그래밍이 불가능한가? 그렇지는 않다.
    • 자바도 `` map, filter, reduce`` 같은 함수형 프로그래밍의 기본 단위 함수를 지원하고 있다.
      • 이 함수들은 "지연 평가" 되도록 제대로 만들어져 있다.
  • 그래서 이런 객체 지향 언어에도 함수형을 적용하려면, 함수형을 어떻게 바라봐야 하는가?
    • 객체 지향이 하고자 하는 것은 큰 문제를 나누는 것이다. 
    • 근데 결국 나누고 나서 어떤 30대 이상의 남자만 뽑자라는 것은 결국 for, if를 써야한다.
    • 함수형이 해결하고자 하는 것은 이런 for, if를 비롯한 가장 기본이 되는 구성 요소들에 대한 생각을 모두가 이해할 수 있는 규약으로 통일하자는 것이다.
    • 우리가 코딩을 할 때 틀린 코드를 작성하는 상황은 클래스를 설계하고, DB를 연결하는 과정이 아니라, i++이냐 ++i냐와 같은 간단한 상태변화에서 주로 발생한다. 
    • 그래서 가장 간단하고 틀릴리가 없는 함수들의 조합인, map filter reduce take groupBy 같은 것들의 조합을 통해 코딩을 하자는게 함수형의 컨셉이다. 
      • 그래서 함수형으로 사고한다는건 기존의 명령형 프로그래밍을 할 때의 기본 구성 요소인 for, if부터 사고를 시작하는게 아니라 for, if같은 것들을 잊고 filter, map, reduce가 제일 기본 구성 요소라고 생각하고 사고를 시작하는 것이다.
    • 결국 함수형은 reduce, map, filter 같은 작은 단위의 순수 함수들을 프로그래밍의 기본 구성 요소로 간주하면서 프로그래밍 하는 사고와 방식 이며, 이를 통해 신뢰성 있는 프로그램을 작성하자. 라고 볼 수 있다.
      • 또한 이러한 순수 함수들이 단순한 유틸리티 함수가 아니라, 언어의 기본 구성요소라는 중대한 의미를 갖는다. 

 

그럼 어느 단위에서 객체지향을 사용하고, 어느 단위부터 함수형을 사용해야 하는가?
  • 어느 정도 수준 까지 함수형을 적용할 것인가에 대한 정답은 없다. 
    • 언어가 어느 정도 차원까지 함수형을 지원하느냐에 따라서 선택적으로 도입하는 수 밖에 없다.
  • 자바같은 언어는 아무래도 메서드 내에서 map filter reduce 같은 것을 써서 상기한 프로그래밍 기본 구성 요소에 대한 사고를 통일하자는 느낌으로 쓰는게 좋은 것 같고,
  • 하스켈같은 함수형 언어는 아예 사용자 정의 객체를 만들지 않으면서 코딩할 수도 있다. 
  • 자바스크립트도 오히려 객체 지향에 대한 지원이 미흡해서 아예 함수형으로 코딩하는 것이 대세다. 
    • 백엔드 쪽도 그렇고, 프론트엔드 쪽도 그렇다. 리액트 같은 라이브러리도 `` ClassComponent``보다는 `` FunctionComponent``를 장려하는 분위기다.
  • 함수형 프로그래밍이 항상 옳은 것은 아니다. 함수형이 좋냐 명령형이 좋냐는 해결해야 하는 문제의 성격에 따라 다르다.
    • 명령형 방식이 더 적합하다면 객체의 상태를 변경하고 Side effect를 활용하는 함수를 사용하면 된다. 
    • 요즘은 큰단위는 객체지향, 메소드 내에서는 함수형 처럼 적당히 절충해서 쓰는 경우가 많다.
 
 

함수형 프로그래밍을 가능하게 하는 요소들

일급 시민(일급 객체, first-class) 함수
일급 객체는 언어상 사용에 제약이 없는 객체를 의미한다. 변수에 대입하거나 인수로 넘길 수 있어야 하며 리턴할 수도 있다면 일급 객체다. 
함수형에서는 함수가 일급 객체이므로, 함수를 변수에 대입하거나 인수로 넘기거나, 함수 내에서 새로운 함수를 만들어 반환할 수 있다.
* 전통적인 OOP라면 함수를 인자로 전달할 수 없기 때문에, 객체를 만들어서 넘기고 그 객체를 이용해 함수에 접근해야 한다.
 
함수를 리턴할 수 있다는 점은 상당히 강력한 요소인데, 비슷한 함수이지만 세부 사항에서 차이가 있을 때 함수(e.g., lambda)를 인자로 전달해 새로운 함수를 반환할 수 있다.
(명령형이라면 이를 처리하기 위해 인자를 추가로 받거나, 상속 등을 사용해야 하는데 전혀 다른 패러다임이다..)
 
이렇게 람다나 다른 함수를 인자로 받거나 함수를 반환하는 함수를 고차 함수(HOF, High Order Function)라고 부른다.
고차 함수는 기본 함수를 조합해서 새로운 함수를 만들 수 있다. 이렇게 만들어진 새로운 함수를 다시 조합하는 식으로 더 복잡한 연산을 쉽게 정의할 수 있다는 장점이 있다. 이런 식으로 고차 함수와 단순한 함수를 조합해서 코드를 작성하는 기법을 컴비네이터 패턴(combinator pattern)이라 부르고, 컴비네이터 패턴에서 복잡한 연산을 만들기 위해 값이나 함수를 조합할 때 사용하는 고차 함수를 컴비네이터(combinator)라고 한다.
 
불변 객체(immutability object)
일단 만들어지고 나면 내부 상태(e.g., 멤버 변수)가 변경되지 않는 불변 객체를 사용한다.
즉, 객체 변수의 값은 ``__init__``에서 초기화 하고 계속 유지해야 한다.
 
불변 객체의 장점은 다중 스레드를 사용해도 안전하다는 것이다. 불변 데이터 구조를 사용하고 순수 함수를 그 데이터 구조에 적용한다면 다중 스레드 환경에서 같은 데이터를 여러 스레드가 변경할 수 없다. 따라서 동기화 부담을 덜 수 있다.
 
Side effect 없음, pure function
같은 input에 대해 항상 동일한 output을 내놓고, 함수 외부의 변수, 함수와 상호작용하지 않는 순수 함수를 사용한다.
 
Side-effect가 제거된 순수 함수는 함수 내부에 숨겨진 입출력이 존재하지 않아야 한다.
숨겨진 입력은 파라미터로 넘겨받지 않은 어떤 값을 내부적으로 생성하는 것을 말한다. ``js new Date()`` 등으로 직접 할당받는 경우가 그렇다. 
숨겨진 출력은 리턴이 아닌, 외부의 변수에 직접 접근하는 것을 말한다. 메서드에서 객체 변수에 접근하는 경우가 그렇다.
숨겨진 입출력을 제거한 순수 함수는 어느 시점에 호출하든 같은 입력에 대해 같은 출력을 반환하게 된다.
``js add(x, y)``는 순수 함수의 좋은 예다.
 
숨겨진 입출력을 제거하는 과정에서, 내부적으로 어떤 값을 생성하지 않으려면 파라미터로 입력받아야 한다. 따라서 받아야 하는 인수 개수가 늘어날 수 있다. 이는 복잡해 보일 수 있지만, 실제로는 프로그램을 더 간결하게 해준다.
 
``self``나 ``this``가 인자로 넘어가므로 메서드에서 객체 변수를 사용하는 것은 괜찮지만 객체 변수는 처음에 초기화 된 값 그대로 변경되지 않아야 한다.
 
Side effect가 존재하는 함수는 그 함수를 실행하기 위해 필요한 전체 환경을 구성하는 준비 코드가 따로 필요하다. 그래서 남이 짠 코드 조각을 테스트할 때 귀찮은 일이 자주 발생하곤 한다. 
그러나 순수 함수는 준비 코드 없이 독립적으로 테스트할 수 있다.
 

함수형 네이밍 (naming)

  • 보통 명령형에서는 메서드가 객체의 상태를 변화시키기 마련이라, `` add``같은 동사를 사용한다.
  • 그러나 함수형에서는 상태를 가지는 객체로 프로그래밍을 하는 것이 아니라, 상태를 가지지 않는 데이터와 함수 2가지를 이용해 프로그래밍한다. 
  • 그래서 상태를 변화시키지 않는다는 것을 명시하기 위해 `` plus`` 같은 부사를 사용한다.