저는 NgRx를 쓰면서 처음 플럭스(Flux)구조를 접했어요. 플럭스의 첫인상은 상당히 복잡해보였죠. 하지만 가만보니 대수구조(algebraic structure)가 리스트(list) 자료구조와 같다는걸 알아챘어요. 그로써 복잡해보이는 구조를 더 간단한 구조로 투영해서 볼 수 있었고 결과적으로 더 쉽고 빠르게 적응할 수 있었어요. 저는 이번 글에서 바로 이 경험을 공유해볼거에요.
글을 읽기 위한 배경 지식
약간의 수학: Group 정도만 아셔도 돼요.
Flux(선택): 마지막에 Flux를 예로 들거라서 아시면 좋아요.
프로그래머에게 Algebra는 무엇인가?
Algebra A 를 만족하는 타입 T의 정의는 "T를 Algebraic하게 생성하는 방법" 을 물어보고, 답을 eval
이라는 함수로 옮겨 적는것과 같아요. Group 을 예로 하나 들어볼게요. 그룹의 수학적 정의는 다음과 같아요.
이제 위에서 말한 질문을 스스로에게 던져보세요. T 를 algebraic하게 생성하는 방법은 뭐가있을까요? 그룹의 정의에 따르면 다음과같은 세가지 방법밖에 없어요.
- binary 연산자
- unary 연산자
- nullary 연산자
identty 오브젝트를 nullary 함수로 표현한 부분을 주의해주세요. id 의 도메인을 1로 표현한 이유는 타입은 집합이고, 집합의 유일한 identification 은 cardinality(element 의 갯수)이기 때문에 단순히 숫자로 표현하는것에 전혀 문제가 없기 때문이에요.
셋을 piecewise 함수로 합친 함수가 바로 Algebra의 정의에요.
예로 그룹 Mod 5와 (Integers) 를 각각 eval로 정의해볼게요.
Mod N
의 eval
과
의 eval
의 signature 는 둘다
로 그룹이기 때문에 같아요. 하지만 구현은 다른 Algebra이기 때문에 다르죠.
Mod N
의 eval
은
을
의 결과값으로(여기서 + 는 Mod N
의 덧셈),
는
의 결과값으로,
는
으로 맵핑하고
의 eval
은
을
의 결과값으로,
을
의 결과값으로,
를
로 맵핑해요.
리스트 Algebra
이제 Algeba에 대해서 조금 알았으니 좀더 실용적으로 접근해봅시다. 프로그래머들이 의식하진 않지만 매일매일 쓰는 Algebra가 하나 있어요. 바로 List E
에요. 어떤 타입 E의 List 도 그룹과 같은 Algebraic 구조에요. List E
를 Algebra로 정의하기위해 그룹에서 했던것처럼 다음과같은 질문으로 시작해 볼게요. List E
를 어떻게 algebraic 하게 생성할 수 있을까요? 프로그래머들은 List를 정의할때 필수로 다음 두 생성자를 먼저 만들어요.
- 비어있는 리스트
- 기존 리스트 에 새로운
e
가 추가된 리스트
이 두 생성자를 operator로 사용해서 위 Group처럼 정의해볼게요.
여기서 는 타입 E의 cardinality 를 뜻하는 상수에요.
첫번째 생성자는
에 해당하고 두번째 생성자는
에 해당해요. List E
에 conform 할 algebra의 eval
함수의 signature 는
가 되겠죠.
우리는 List E
에 conform 하는 두개의 algebra를 만드거에요. 하나는
가 0으로 evaluate되고
는
로 evaluate 되게끔 구현하고 Len 이라고 부를거에요. 여기서 E는 어떠한 타입이든 될 수 있고 T는 이미
가 0으로 evaluate 한다는 점에서 Int로 결정된거에요(type inference). 다른 하나는
가
으로 evaluate되고
는
로 evaluate 되게끔 구현하고 Sum이라고 부를거에요. 여기서
의 의미는 타입 E의 0에 해당하는 element를 뜻하고요 마찬가지로
의 결과 타입이 E라는 점에서 이미 T도 E라는 점이 결정이 된거에요.
신기하게도 Sum Algebra의 evaluation은 리스트에 있는 모든 E의 합이되고, Len Algebra의 evaluation은 리스트가 포함하는 element의 갯수 (리스트의 길이)가 돼요. 둘의 eval
함수의 구현을 들여다 보고 어떻게 이럴 수 있는지 알아볼게요.
Len의 eval은 다음과 같아요.
Sum의 eval은 다음과 같아요.
두 Algebra 모두 두가지 정보를 이용해서 스스로를 정의해요. 하나는 초기값이고, 다른 하나는 리스트 element의 타입 E와 결과 타입 T를 받는 evaluation 함수
죠. 뭔가 익숙하지 않나요?
class Foldable t where
foldr :: (a -> b -> b) -> b -> t a -> b
프로그래머들이 매일매일 쓰는 fold
(aka reduce
) 함수가 바로 두가지 정보를 받아서 List E
Algebra를 구현해주는 함수였습니다! (정확히 말하면 오른쪽부터 접는 foldr
/reduceR
) fold 를 쓸때마다 프로그래머는 항상 새로운 Algebra를 정의하고 있었던거죠.
Flux
Redux나 NgRx를 써보신 분들중 아마 List와 비슷하다는 점을 눈치채신 분들도 계실거에요. 특히나 초기 App State를 설정해주는 부분과 새로운 App State를 계산하는 부분을 reducer라고 불리는 부분이 리스트가 reduce를 사용하는것과 많이 일치하죠. 각각 다른 파라메터를 받는 세개의 dispatcher를 가진 Flux를 정의해볼게요:
이 Algebra의 eval 함수는 다음과 같겠죠:
타입은 집합이잖아요? distributive 성질을 이용해서 하나로 합치면:
List E
Algebra 로 바꿀 수 있어요. Flux도 결국 List Algebra 였던거죠.
위 내용은 F-Algebra라는 수학적 이론을 바탕으로 쓰여졌어요. F-Algebra에 대해 공부해보고싶다면 여길 추천:
Category Theory for Programmers: F-Algebra
전반적인 카테고리 이론을 공부하고 싶다면 여길 추천:
Category Theory for Programmers
Math3ma: Category Theory
Top comments (0)