Chapter 1. 쏙쏙 들어오은 함수형 코딩에 오신 것을 환영합니다

함수형 프로그래밍은 무엇인가요?

함수형 프로그래밍은 범용 프로그래밍 패러다임이다. 이는 한마디로 설명하는 것은 어렵지만, 이미 많은 분야에서 사용된다. 하지만 많은 자료가 학계에서 만든 자료라 일반 개발자가 이해하기 어렵고 실용적이지 않다. 이 책에서는 주로 실무에서 쓸 수 있는 함수형 프로그래밍 기술을 다룬다.

그렇다면 함수형 프로그래밍은 무엇인가? 함수형 프로그래밍의 정의는 다음과 같다.

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

  • 수학 함수를 사용하고 부수 효과(side effect)를 피하는 것이 특징인 프로그래밍 패러다임
  • 부수 효과 없이 순수 함수(Pure Function)만 사용하는 프로그래밍 스타일

부수 효과는 함수가 리턴값 이외에 하는 모든일을 말한다. 예를 들어, 메일 보내기나 전역 상태 수정하기 같은 일이 부수 효과이다. 부수 효과는 함수를 부를 때마다 발생하기 때문에 문제가 될 수 있다. 어떤 경우에는 리턴값이 필요해서 불렀지만 의도하지 않게 부수 효과가 발생할 수 있다. 대부분의 함수형 프로그래머는 불필요한 부수 효과를 가능한 사용하지 않으려고 한다. 이는 단위 테스트를 어렵게 하고, 기능의 확장이나 분리 등 유지보수하기 어렵게 만든다.

순수 함수는 인자에만 의존하고 부수 효과는 없는 함수이다. 인자에만 의존한다는 말은 같은 인자를 넣으면 항상 같은 결과를 돌려준다는 말이다. 수학에서 함수도 그렇게 동작하기 때문에 우리는 순수 함수를 수학 함수라고 볼 수 있다. 순수 함수는 다루기 쉽고 이해하기 쉽기 때문에 함수형 프로그래머들이 중요하게 생각한다. 정의에 따르면 함수형 프로그래머는 항상 부수 효과를 피하고 순수 함수만 사용해야 할 것 같지만, 실제로는 부수 효과와 순수하지 않은 함수를 사용하게 된다.

실용적인 측면에서 함수형 프로그래밍 정의의 문제점

앞에서 살펴본 함수형 프로그래밍의 정의는 실제 프로그래밍에 큰 도움이 되지 않는다.

문제 1: 부수 효과는 필요하다.

정의에 따르면 함수형 프로그래밍은 부수 효과를 피해야 하지만, 부수 효과는 소프트웨어를 실행하는 이유이다. 이메일을 전송하지 않는 이메일 전송 소프트웨어가 무슨 의미가 있겠는가? 정의와 달리 부수 효과가 필요할 때는 써야 한다.

문제 2: 함수형 프로그래밍은 부수 효과를 잘 다룰 수 있다.

정의에는 순수 함수만 쓰라는 것처럼 되어 있지만, 실제로 함수형 프로그래머는 순수하지 않은 함수도 사용하며, 이를 잘 다룰 수 있는 기술이 많이 있다.

문제 3: 함수형 프로그래밍은 실용적이다.

함수형 프로그래밍이 수학적이라 실제 소프트웨어 개발에서 사용하지 않는 것처럼 느껴지지만, 함수형 프로그래밍으로 잘 만들어진 소프트웨어가 많이 있다. 그래서 위 정의는 함수형 프로그래밍을 시작하려는 사람에게 혼란을 준다.

함수형 프로그래밍을 학문적 지식이 아닌 기술과 개념으로 보기

이 책에서는 일반적인 함수형 프로그래밍의 정의를 쓰지 않는다. 그런 이론적인 내용보다는 실제 함수형 프로그래머가 가진 기술과 생각의 흐름, 시각에 집중하면서 실용적인 측면에 집중하자.

액션과 계산, 데이터 구분하기

함수형 프로그래머는 직감적으로 코드를 세 분류로 나눈다.

  1. 액션
  2. 계산
  3. 데이터
{"firstname": "Eric", "lastname": "Normand"}	// 사람에 대한 정보
*sendEmail(to, from, subject, body)						// 이메일을 보내는 코드, 사용할 때 조심해야 한다.
sum(numbers)																	// 모든 숫자를 더하는 편리한 함수
*saveUserDB(user)															// 데이터베이스에 저장하면 다른 시스템에서 해당 데이터 조회가능
string_length(str)														// 같은 문자열을 넣으면 항상 같은 길이를 준다.
*getCurrentTime()															// 부를 때마다 다른 시간을 준다.
[1, 10, 2, 45, 3, 98]													// 그냥 숫자 리스트

함수형 프로그래머는 부를 때 조심해야 하는 코드를 구분한다

액션: 부르는 시점에 의존

*sendEmail(to, from, subject, body)						// 이메일을 보내는 코드, 사용할 때 조심해야 한다.
*saveUserDB(user)															// 데이터베이스에 저장하면 다른 시스템에서 해당 데이터 조회가능
*getCurrentTime()															// 부를 때마다 다른 시간을 준다.

액션이 아닌 것: 부르는 시점이나 횟수가 중요하지 않은 코드

{"firstname": "Eric", "lastname": "Normand"}	// 사람에 대한 정보
sum(numbers)																	// 모든 숫자를 더하는 편리한 함수
string_length(str)														// 같은 문자열을 넣으면 항상 같은 길이를 준다.
[1, 10, 2, 45, 3, 98]													// 그냥 숫자 리스트

위의 분류는 매우 중요하다. 액션은 호출하는 시점과 횟수에 의존하므로, 호출할 때 조심해야 한다. 반면, 액션이 아닌 코드는 비교적 사용하기 쉽다. 예를 들어, sum함수는 호출하는 시점이 중요하지 않으며, 언제 호출해도 항상 같은 값을 준다. 호출하는 횟수 역시 중요하지 않다. 또한 나머지 코드나 소프트웨어 외부에 영향을 주지 않기 때문에 여러 번 호출해도 상관없다.

함수형 프로그래머는 실행하는 코드와 그렇지 않은 코드를 구분한다

계산이나 데이터는 둘 다 부르는 시점이나 횟수가 중요하지 않다. 다만 실행 여부에 있어서 차이가 있다. 계산은 실행 가능하나 데이터는 그렇지 않다. 데이터는 정적이고 보이는 그대로이지만 계산은 실행하기 전까지 어떻게 동작할지 알 수 없다.

액션: 부르는 시점에 의존

*sendEmail(to, from, subject, body)						// 이메일을 보내는 코드, 사용할 때 조심해야 한다.
*saveUserDB(user)															// 데이터베이스에 저장하면 다른 시스템에서 해당 데이터 조회가능
*getCurrentTime()															// 부를 때마다 다른 시간을 준다.

계산: 입력값을 계산해 출력하는 것

sum(numbers)																	// 모든 숫자를 더하는 편리한 함수
string_length(str)														// 같은 문자열을 넣으면 항상 같은 길이를 준다.

데이터: 이벤트에 대한 사실을 기록한 것

{"firstname": "Eric", "lastname": "Normand"}	// 사람에 대한 정보
[1, 10, 2, 45, 3, 98]													// 그냥 숫자 리스트

액션과 계산, 데이터를 구분하는 것은 함수형 프로그래밍의 기본 개념이다. 많은 함수형 프로그래밍 기술과 개념은 코드를 액션, 계산 데이터로 구분하는 것으로분터 시작한다.

코드를 만들 때 액션과 계산, 데이터 모두 중요하지만, 각각 장단점을 가지고 있다. 따라서 잘 알고 적절하게 쓰는 것이 좋다. 일반적으로 액션 보다는 계산이 쓰기 쉽고, 계산 보다는 데이터가 쓰기 쉽다.

함수형 프로그래머는 액션과 계산, 데이터를 구분한다

프로젝트 관릴르 위해 클라우드 서비스를 만든다고 생각해보자. 여러 클라이언트가 작업 완료 표시를 하면 서버에서 이메일을 통해 알려주는 서비스이다. 이 서비스에서 액션과 계산, 데이터는 어떤 것일까?

단계 구분
1단계: 사용자가 작업 완료 표시를 함 UI 이벤트는 실행 횟수에 의존하므로 액션
2단계: 클라이언트가 서버로 메시지를 보냄 메시지를 보내는 것은 액션
메시지 자체는 나중에 서버에서 해석해야 하는 값이므로 데이터
3단계: 서버가 메시지를 받음 메시지를 받는 것은 횟수에 의존하므로 액션
4단계: 서버가 데이터베이스를 변경 내부 상태를 바꾸는 것은 액션
5단계: 서버가 누구에게 알림을 보낼지 결정 결정하는 것은 계산, 입력값이 같다면 서버는 항상 같은 결정
6단계: 서버가 이메일로 알림을 보냄 이메일 보내기는 액션, 같은 메일은 여러 번 보내는 것은 다름

함수형 프로그래밍에서는 코드를 세 가지로 분류한다

액션

  • 특징: 액션은 실행 시점이나 횟수 또는 둘 다에 의존한다.
  • 관련 기술:
    • 시간이 지남에 따라 안전하게 상태를 바꿀 수 있는 방법
    • 순서를 보장하는 방법
    • 액션이 정확히 한 번 만 실행되게 보장하는 방법

계산

  • 특징: 계산은 입력값으로 출력값을 만드는 것이다. 같은 입력값으로 계산하면 항상 같은 결괏값이 나온다. 언제, 어디서 계산해도 결과는 같고 외부에 영향을 주지 않는다. 계산은 테스틑하기 쉽고 언제든지 몇 번을 불러도 안전하다. 이는 보통 자바에서 유틸클래스에 존재하는 static 메서드들을 기본적으로 떠올려볼 수 있다.
  • 관련 기술:
    • 정확성을 위한 정적 분석
    • 소프트웨어에서 쓸 수 있는 수학적 지식
    • 테스트 전략

데이터

  • 특징: 데이터는 이벤트에 대해 기록한 사실이다. 데이터는 실행하는 코드만큼 복잡하지 않기 때문에 다른 것과 구분된다. 알아보기 쉬운 속성으로 되어 있고 실행하지 않아도 데이터 자체로 의미가 있다. 또 같은 데이터를 여러 형태로 해석할 수 있다.
  • 관련 기술:
    • 효율적으로 접근하기 위해 데이터를 구성하는 방법
    • 데이터를 보관하기 위한 기술
    • 데이터를 이용해 중요한 것을 발견하는 원칙

액션, 계산, 데이터를 구분하면 어떤 장점이 있는가?

함수형 프로그래밍은 요즘 유행하는 분산 시스템에 잘 어울린다

함수형 프로그래밍은 단순한 트렌드가 아니다. 오랫동안 쌓아온 수학 지식을 기반으로 하는 가장 오래된 프로그래밍 패러다임이다. 최근 들어 클라우드 서버 등의 기술이 확산되면서 네트워크 통신을 하는 복잡한 소프트웨어가 필요하게 되었고, 그런 이유로 함수형 프로그래밍이 다시 각곽받게 된 것이다.

여러 컴퓨터가 네트워크를 통해 통신하면서 처리해야 할 메시지는 순서가 섞일 수 있고 중복되거나 유실되기도 한다. 시간에 따라 바뀌는 값을 모델링할 때 동작 방법을 이해하는 것은 중요하지만 쉽지 않다. 실행 시점이나 횟수에 의존하는 코드를 없애면 코드를 더 쉽게 이해할 수 있고 심각한 버그를 막을 수 있다.

데이터와 계산은 실행 시점이나 횟수에 의존하지 않는다. 그래서 액션이 많은 코드를 데이터와 계산을 바꿀수록 분산 시스템에서 생기는 여러 가지 문제를 쉽게 해결할 수 있게 된다.

액션은 실행 시점과 횟수에 의존하기 때문에 여전히 문제가 되지만, 코드 전체에 영향을 주지 않도록 격리시키면 된다. 또 분산시스템이 아무리 불확실성을 가지고 있다고 해도 액션을 안전하게 다룰 수 있는 기술이 있기 때문에 안심할 수 있다. 그리고 코드의 많은 부분을 액션에서 계산으로 옮기면 결과적으로 액션도 다루기 쉬워진다.

분산 시스템 규칙 3가지

  • 메시지 순서가 바뀔 수 있다.
  • 메시지는 한 번 이상 도착할 수도 있고 도착하지 않을 수도 있다.
  • 응답을 받지 못하면 무슨 일이 생겼는지 알 수 없다.

MSA환경이나, RabbitMQ, Kafka와 같은 메시지 브로커를 통한 모듈간의 통신에서 함수형 프로그래밍이 도움을 줄 수 있을 것 같다.

함수형 사고란 무엇인가?

함수형 사고는 함수형 프로그래머가 소프트웨어 문제를 해결하기 위해 사용하는 기술과 생각이다. 가장 중요한 개념 두 가지는 다음과 같다. 첫 번째는 액션과 계산, 데이터를 구분해서 생각하는 것이다. 두 번째는 일급 추상(First-Class Abstraction)이라는 개념이다.

결론

함수형 프로그래밍 기술과 원칙에 대한 지식은 방대하나, 그 모든 것은 액션과 계산, 데이터를 구분하는 것부터 시작한다. 함수형 프로그래밍에 대한 설명을 보고 생각해보면 이는 객체지향 프로그래밍(Object-Oriented Programming)과 이율배반적이라고 생각할 필요는 없는 것 같다. 오히려 객체를 잘 분리하고 로직의 흐름에 있어서 함수형 프로그래밍을 잘 적용시키면 객체지향적으로도 함수형 프로그래밍적으로도 깔끔한 코드를 작성할 수 있을 것 같다.