본문 바로가기

Python

함수형 프로그래밍

함수형 프로그래밍이란?

 

함수는 인풋만 같으면 다른 요소의 영향을 받지 않고 아웃풋이 항상 안정적으로 출력된다.

 

감자 튀김을 만들기 위해서, 감자를 깎는 사람, 채써는 사람, 튀기는 사람을 전문적으로 훈련시켜 놓고 그 위치에서 정해진 업무만 시킨다면, 효율성이 오를 수 있다. 인풋으로 고구마를 넣어도 같은 프로세스와 결과 값을 얻을 수 있을 것이다. 

 

마찬가지로, 함수형 프로그래밍도 특정 상황에서 필요한 함수를 꺼내서 사용하여 코드의 효율성을 높일 수 있다.

 

 

1. 함수형 언어에서는 변수는 변경되지 않는다. 

분류 비함수형 프로그래밍 함수형 프로그래밍
Code public class Squint {
     public static void main(String args[]) {
           for (int i=0; i<25; i++)
              System.out.println(i*i);
   }
}
println( (take 25 (map (fn [x] (* x x)) (range)))) result = list(map(lambda x: x * x, range(25)))
print(result)
Comment 자바 언어로 작성한 코드 클로저(Clojure)로 작성한 코드 파이썬으로 작성한 코드
Details * i와 같은 가변 변수를 사용한다. * println, take, map, range 모두 함수
* 가변 변수가 없다.
* x는 변수가 한 번 초기화되면 변하지 않는다.
* list, map, lambda, range 모두 함수
* 가변 변수가 없다.
* x는 변수가 한 번 초기화되면 변하지 않는다.

 

 

2. 불변성과 아키텍처

Question

왜 변수의 가변성을 염려해야할까?

 

Answer

경합 조건, 교착 상태 조건, 동시 업데이트 문제가 모두 가변 변수로 인해 발생하기 때문이다. 

동시성 애플리케이션(다수 스레드, 프로세스를 사용하는)에서 마주치는 모든 문제는 가변 변수가 없다면 절대로 발생하지 않는다. 

 

* 경합 조건 예시

가변 데이터를 동시에 연산 작업을 수행하는 상황에서 데이터 정합성이 보장되지 않는 간단한 예시

예금 조회 100만원 (스레드가 공유하는 가변 변수)
스레드 Thread 1 Thread 2
시간 흐름 입금 : 100만원 -
- 입금 : 50만원
- 저장 : 150만원
저장 : 200만원 -
예금 조회 200만원 (?)

 

---> 레디스의 분산락을 활용하면 위의 동시성 이슈를 제어할 수 있다. 전 회사에서 Medis로 레디스를 사용하고 있었는데, 마케팅 데이터 처리에서도 정합성 보장을 위해서 사용했던 것 같다. 

 

 

3. 가변성의 분리

Question

불변성은 실현 가능한가?

 

Answer

가능하지만 일종의 타협이 필요하다.

 

 

-> 가장 주요한 타협은,
애플리케이션 또는 애플리케이션 내부의 서비스를 가변 컴포넌트와 불변 컴포넌트로 분리하는 일이다.

* 불변 컴포넌트에서는 순수하게 함수형 방식으로만 작업이 처리되며, 어떤 가변 변수도 사용되지 않는다. 

* 불편 컴포넌트는 변수의 상태를 변경할 수 있는 즉 순수 함수형 컴포넌트가 아닌 하나 이상의 다른 컴포넌트와 서로 통신한다. 

 

-> 상태 변경은 컴포넌트를 갖가지 동시성 문제에 노출하는 꼴이므로,

트랜잭션 메모리와 같은 방식을 통하여 동시 업데이트와 경합 조건 문제로부터 가변 변수를 보호한다. 

 

* 상황 예시

예금 조회 100만원 (스레드가 공유하는 가변 변수)
스레드 Thread 1 Thread 2
시간 흐름 입금 : 100만원 -
- 입금 : 50만원
- 저장 : 150만원
저장 : 200만원 -
트랜잭션 메모리
입금 : 100만원 -
저장 : 200만원 -
- 입금 : 50만원
- 저장 : 250만원
예금 조회 250만원

 

* 코드 예시

클로저 파이썬
(def counter (atom0)) ; counter를 0으로 초기화한다.
(swap! counter inc) ; counter를 안전하게 증가시킨다.
import threading

counter = 0
counter_lock = threading.Lock()

def increment_counter():
    global counter
    with counter_lock:
        counter += 1

# counter를 0으로 초기화
counter = 0

# 안전하게 counter 증가
increment_counter()
atom은 변경 불가능한 데이터를 가변적으로 사용할 수 있도록 하는 메커니즘 (여러 스레드 사이에서 안전하게 값을 변경)

swap! 함수를 사용하여 atom의 값을 변경할 수 있음
inc 함수는 숫자를 1씩 증가시키는 함수이고,
따라서, counter 값을 1만큼 증가시키는 작업을 안전하게 수행
hreading 모듈의 Lock 클래스를 사용하여
counter 변수를 안전하게 증가시킴

with 블록 내에서 Lock을 사용하여 보호하고,
데이터의 일관성을 유지하며 스레드 간의 동시 접근 문제를 방지

 

 

4. 이벤트 소싱

* 이벤트 소싱이란, 상태가 아닌 트랜잭션을 저장한다는 내용이다. 
즉, 위에서 초기 예금 금액인 100만원에서 이벤트가 발생할 때마다 생기는 변경을 초기 금액의 상태 자체에 저장하는 것이 아니라,

Thread1, Thread2와 같은 이벤트(트랜잭션) 자체를 저장하여, 조회시에는 상태 값을 조회하는 것이 아니라, 

저장된 이벤트들을 통해 상태 값을 재구성하여 조회하는 방식으로 진행된다는 것이다. 

 

 

 

애플리케이션에서는 CRUD가 아니라 CR만 수행되어, 데이터 저장소에서 변경과 삭제가 전혀 발생하지 않으므로

동시성 업데이트 문제 또한 발생하지 않는다. 

 

즉, 저장 공간과 처리 능력만 충분하면 애플리케이션이 완전한 불변성을 갖도록 만들 수 있고, 따라서 완전한 함수형으로 만들 수 있다.