ETC

함수형 프로그래밍에 대해

partner_jun 2022. 2. 26. 11:31

예전에 공부하다 남긴 자료(이 블로그에 흔적으로 남아있는) 것들을 최대한 간략하게 요약한 내용.

대부분의 개발자는 수학과 출신 개발자가 아니므로, 수학적 이론을 기반으로 한 학습(탑다운)이 아닌, 실제 구현된 내용으로부터 이론을 알게 되는(바텀업) 경우가 많아 그 측면에서 설명하고자 했다.

팀 슬랙에 공유했었는데 그냥 버리긴 뭔가 아까워서 글로 작성해둔다.

 



함수형 프로그래밍

함수형 프로그래밍이란?

절차적, 객체지향과 같은 언어 패러다임으로 상위 요소나 전역에 의존성이 없는 순수한 함수를 만들고, 그 함수들을 조합함으로써 프로그램을 만들어 내는 프로그래밍 방식. 문제의 요소를 간단한, 처리하기 쉬운 형태의 함수 단위로 나누기 때문에 어려운 문제를 쉽고 사이드 이펙트 없이 풀어낼 수 있다고 주장한다. 함수형 프로그래밍에서 사용되는 대부분의 이론은 대수학의 카테고리 이론(범주론)에서 기반한다.

카테고리 이론

수학의 여러 분야에서 공통적으로 발생하는 현상들을 추상화하여 정리하던 분야. 하지만 그 추상화된 이론에서부터 각 세부 분야의 핵심 문제들을 해결해 낼 수는 없었음. 하지만 컴퓨터사이언스에서의 대수적 개념은 그 이론의 요소들을 이용해 문제를 해결해낼 수 있어 유용하게 사용됨.


1. Functor

Map의 추상화된 개념

다른 것으로 '매핑'될 수 있는 성질을 가진 대수적 자료형

 

예시

[2, 4, 6].map(x => x * 2); 
// 이 예제에서 [2, 4, 6]은 Functor다.

 

 

2. Applicative

함수를 감싼 Functor

 

예시

 

const funcs = [x => x * 2, y => y * 3] // Applicative
const values = [1, 2, 3, 4]

values.map(x => funcs.map(f => f(x)))

 

 

3. Monad

FlatMap의 추상화된 개념

한마디로 합성, 두 개의 (합성가능한) 컨텍스트를 하나의 컨텍스트로 합치는 것

 

예시

const l1 = [1,2,3,4]
const l2 = [a,b,c,d]

// l1 + l2를 합치려면?
l1.map(v1 => l2.map(v2 => v1 + v2))
  .reduce((total, arr) => total.concat(arr)) 
  
['1a', '1b', '1c' .... '4d'] // l1 + l2 (합성) 결과

 

 

왜 모나드가 필요한가?

함수만을 프로그래밍(FP) 하는 경우에

f(x) = 2 * x
g(x,y) = x / y

두 함수 f, g가 위와 같이 선언되어 있을 때, 먼저 실행되는 함수를 어떻게 정할 수 있을까?

  • 함수를 합성한다.
  • 예를 들어, 함수 g를 실행한 후 함수 f를 실행하고 싶다면 f(g(x,y)) 형태로 사용하면 된다.

하지만! '실패'할 수 있는 함수 - g(2,0)과 같이- 의 경우에는 어떨까?

  • FP에서는 '예외'가 없다(모든 함수는 사이드 이펙트가 없이 완벽하기 때문). 어떻게 이를 해결할 수 있을까?
    • 실패하는 경우를 포함한, 두 개의 리턴 값을 가지는 함수로 만든다.
g : Int, Int => Int | null

하지만! 함수는 하나의 값만을 리턴해야 한다.

  • 'boxing type'이라고 부를 수 있는, Int와 null 값을 가질 수 있는 새로운 데이터 타입을 만든다.
g: Int, Int => Maybe<Int>

다시 돌아가서, f(g(x,y))는 어떻게 되었을까? 함수 f는 Maybe<Int>라는 자료형을 처리할 수 없다.
게다가 함수 g를 포함한 모든 함수가 Maybe<Int> 자료형을 처리하게 해야 한다.
결과적으로 함수를 합성, 체이닝, 연결하는 특별한 함수가 필요해진다.

// Example (Java Style pseudo code) 
g(2, 0)
  .ifPresent(v => f(v))
  .else(() => Inf)

 

FP 언어에서는 이러한 방식으로 데이터를 처리하므로, 언어 문법 레벨에서 지원하고자 모나드로 추상화하여 정의하고, 실제로 사용할 수 있는 구현체들을 만드는 것이다. 당연하게도 문법 레벨이라고 할 지라도 시스템적 지원이 필요한 것이 아니기 때문에 각 언어별로 함수형 프로그래밍을 할 수 있도록 하는 라이브러리가 개발되어 있다. 또한 자주 사용되는 대부분의 언어는 함수형 프로그래밍 자체를 완벽하게 지원하지는 않는다 할지라도, 이러한 개념을 기반으로 만들어진 다양한 메소드나 함수, 타입 등을 지원하고 있다.(이미 모두 사용하고 있다)

 

 

일반 프로그래밍 이론에서 모나드와 가장 가까운 것은 Command Pattern

특정한 값이나 상태를 커맨드 객체에 래핑하고 그 커맨드 객체를 '실행'하는 메소드를 노출하기 때문.
특히 각 커맨드 객체에서 모든 예외를 catch하여 변환할 수 있기 때문에 커맨드 객체간 체이닝이 가능하다.

 

 

 


 

16~17년도 당시 폴리글랏 프로그래밍이니 뭐니 하면서 함수형 프로그래밍이 떠올랐었는데 그 유행을 이끌던 분들은 어디서 뭘 하시는지 가끔 궁금하다. 난 다년간의 경험을 통해 이러한 패러다임을 알아둘 필요는 있지만 실무에는 적용하지 말아야 한다는 생각을 가지게 되었다. 이유는 단순하다. 함수에는 사이드 이펙트가 없지만, 개발자에게 사이드 이펙트가 생긴다. 실무의 코드는 최대한 간결하고 단순하게, 그리고 유지보수 측면에서 작성되어야 한다고 생각한다.