모나드로 풀어보는 최대 구간 합 튜토리얼
초록
리스트와 일반 데이터 구조에 대해 최대 구간 합을 선형 시간에 구하는 방법을, 모나드와 폴드·스캔 기법을 이용해 단계별로 전개한다. 전통적인 삼중 루프 구현을 대수적 구조와 Horner 규칙에 기반한 계산으로 변환하고, 이를 데이터형 일반화와 결합해 추상적인 형태로 확장한다.
상세 분석
본 논문은 고전적인 최대 구간 합(Maximum Segment Sum, MSS) 문제를 함수형 프로그래밍의 대수적 관점에서 재조명한다. 초기 정의는 리스트의 모든 연속 구간을 생성하고, 각 구간의 합을 구한 뒤 최댓값을 선택하는 삼중 중첩 연산으로, 시간 복잡도는 O(n³)이다. 저자는 map·inits·tails와 같은 기본 리스트 연산을 활용해 이를 점진적으로 단순화한다. 먼저 mss = maximum . map (maximum . map sum . inits) . tails 로 변형하고, maximum . map sum . inits 를 foldr h e 형태로 표현하면 tails와의 결합을 통해 scan 레마를 적용해 O(n) 알고리즘을 얻는다. 핵심은 Horner 규칙이 보여 주는 “덧셈 위의 곱셈 분배” 성질을 세미링(semi‑ring) 구조로 일반화하는 것이다. 여기서 ‘덧셈’은 최대 연산(⊔), ‘곱셈’은 정수 덧셈(+)으로 정의된 트로픽 세미링을 사용한다. 따라서 maximum . map sum . inits = foldr (⊕) 0 where u ⊕ z = 0 ⊔ (u + z) 로 표현되며, ⊕ 연산은 상수 시간에 수행된다.
다음 단계에서는 리스트에 국한되지 않고, 임의의 재귀 데이터 타입 µ(F α)에 대해 동일한 구조를 구축한다. ‘tail segment’는 각 노드에 해당 서브터미를 라벨링한 구조 L α = µ(G α) 로 정의하고, subterms와 scan을 파라모픽 형태로 구현한다. 여기서 G α β = α × F 1 β 로 라벨이 하나씩 붙은 변형을 사용한다. ‘initial segment’는 일부 서브터미를 빈 구조(1)로 교체한 형태 H α β = 1 + F α β 로 모델링한다. 이때 빈 구조를 포함하기 위해 모나드 M을 도입한다. M은 리스트, 멀티셋, 집합 등 ‘컬렉션’을 나타내며, 결합 연산 ⊎와 단위 0을 갖는다. prune = fold F (M in H · opt Nothing · M Just · δ₂) 로 정의된 prune은 모든 가능한 초기 구간을 비결정적으로 생성한다. opt는 선택적 삽입을, δ₂는 형태 함자 F 를 모나드 위에 분배한다.
핵심적인 대수적 법칙은 “product over sum”의 분배성이다. F‑알제브라 (β, f)와 M‑알제브라 (β, ⊕/ )가 주어지면, ⊕/ . M f . δ₂ = f . F id (⊕/) 가 성립한다. 이는 fold F ((b ⊕) . f) 로 단일 폴드로 결합될 수 있음을 의미한다. 여기서 b는 빈 구조의 값이며, 실제 계산에선 b의 선택이 결과에 영향을 주지 않는다. 최종적으로 MSS = ⊕/ . contents L . scan F ((b ⊕) . f) . tails 와 같이 표현되며, 모든 연산이 상수 시간에 수행되므로 전체 복잡도는 O(n)이다.
논문은 또한 연습문제와 해답을 통해 독자가 직접 계산 과정을 따라가며, 포인트프리(point‑free) 스타일, 파라모픽 합성, 그리고 모나드 법칙을 실제 코드에 적용하는 방법을 습득하도록 돕는다. 이러한 접근은 프로그램 설계에서 도메인‑특화 언어(DSL)를 호스트 언어(Haskell) 내부에 구현하는 메타‑툴의 사례를 보여주며, 추상화 수준을 높이면서도 효율성을 유지하는 방법론을 제시한다.
댓글 및 학술 토론
Loading comments...
의견 남기기