-
함수형 프로그래밍, 커링, 모노이드, 펑터..Development/Architecture 2026. 2. 18. 18:56반응형
함수형 프로그래밍
순수 함수(Pure function)와 불변 데이터(Immutable data)를 중심으로 프로그래밍을 구성하는 패러다임.
- 순수 함수 : 같은 입력에 대해서 항상 같은 출력을 반환하고, 외부 상태를 변경하지 않는 함수다.
- 불변성 : 데이터를 생성한 후에는 변경이 되지 않고, 변경이 필요하면 새로운 데이터를 만든다.
- 일급 함수(First-Class Function) : 함수를 일반 변수처럼 취급하여 변수 할당, 인자 전달 및 반환값으로 사용 가능하도록 하는 특성이다.
- 고차 함수(Higher-Order Function) : 함수를 인자로 받거나 함수를 반환하는 함수다.
- 함수 합성(Function Composition) : 함수들을 조합하여 다양한 함수를 만들 수 있다.
// 작은 단위 함수들 val removeSpaces: (String) -> String = { it.replace(" ", "") } val toUpperCase: (String) -> String = { it.uppercase() } val addPrefix: (String) -> String = { "PREFIX_$it" } // 합성 유틸리티 infix fun <A, B, C> ((A) -> B).then(next: (B) -> C): (A) -> C = { a -> next(this(a)) } // 함수 합성 val processKey = removeSpaces then toUpperCase then addPrefix println(processKey("hello world")) // PREFIX_HELLOWORLD커링(Currying) : 다중 인자를 갖는 함수를 단일 인자를 갖는 함수 체인으로 변환하는 방법이다.
// 커링: 여러 인자를 받는 함수를 한 인자씩 받는 함수 체인으로 변환 fun <A, B, C> ((A, B) -> C).curried(): (A) -> (B) -> C = { a -> { b -> this(a, b) } } val multiply = { a: Int, b: Int -> a * b } val curriedMultiply = multiply.curried() val double = curriedMultiply(2) // b만 받으면 되는 함수 val triple = curriedMultiply(3) println(double(5)) // 10 println(triple(5)) // 15
모노이드(Monoid)
- 아래 세 가지 조건을 만족하면 모노이드다.
- 결합 법칙(associativity) : (a * b) * c == a * (b * c)
- 항등원(identity element) : a * e == a, e * a == a
- 닫힌 연산(closure) : 같은 타입끼리 연산하면 같은 타입이 나옴.
- 결합 법칙이 성립하기 때문에 데이터를 여러 덩어리로 나누어 각각 계산한 뒤에 마지막에 합쳐도 결과가 동일하다.
- 안전한 병렬 처리의 추상화가 가능하여 대규모 병렬 처리(Map Reduce), 분산 집계 등의 핵심 원리가 된다.
/ 모노이드 인터페이스 interface Monoid<T> { val empty: T // 항등원 fun combine(a: T, b: T): T // 결합 연산 } // 정수 덧셈 모노이드: 항등원 = 0, 연산 = + object IntAddMonoid : Monoid<Int> { override val empty: Int = 0 override fun combine(a: Int, b: Int): Int = a + b } // 문자열 모노이드: 항등원 = "", 연산 = 연결 object StringMonoid : Monoid<String> { override val empty: String = "" override fun combine(a: String, b: String): String = a + b } // 리스트 모노이드: 항등원 = emptyList(), 연산 = + class ListMonoid<T> : Monoid<List<T>> { override val empty: List<T> = emptyList() override fun combine(a: List<T>, b: List<T>): List<T> = a + b }- 교환 법칙은 성립하지 않는다. 위 문자열 모노이드가 그 예시다. ("Hello" 와 "World"는 연산의 순사가 달라지면 결과가 달라진다.)
- 범주론에서 살펴 보자면, 추상화의 대상이 순서가 중요한 결합까지 모두 포함하기 때문이다.
- 원소 그 자체를 바라보는 것이 아니라, 객체와 객체를 변환하는 사상(morphism)으로 이해해야 한다.
펑터(Functor)
- 펑터는 두 카테고리 사이의 구조를 보존하는 매핑을 의미한다.
- 두 카테고리 C와 D가 있을 때, 펑터 F : C -> D는 두 가지 매핑으로 구성된다.
- 대상 매핑 (Object Mapping) : 카테고리 C의 객체 A를 카테고리 D의 객체 F(A)로 보낸다.
- 사상 매핑 (Morphism Mapping) : 카테고리 C의 사상 f : A -> B 를 카테고리 D의 사상 F(f) : F(A) -> F(B)로 보낸다.
- 펑터는 다음의 성질을 만족해야 한다.
- 항등 사상 보존 : F(id_A) = id_F(A)
- 원래의 카테고리에서 아무것도 안 하는 화살표는 옮겨진 카테고리에서도 아무것도 하지 않아야 한다.
- 사상 합성 보존 : F(g * f) = F(g) * F(f)
- f를 가고나서 g를 가는 경로를 한꺼번에 옮긴 것은, 각각을 옮겨서 연결한 것과 같아야 한다.
- 항등 사상 보존 : F(id_A) = id_F(A)
// 펑터 인터페이스 interface Functor<out A> { fun <B> map(f: (A) -> B): Functor<B> } // Maybe — null-safe한 값을 담는 컨텍스트 sealed class Maybe<out A> : Functor<A> { data class Just<A>(val value: A) : Maybe<A>() data object None : Maybe<Nothing>() override fun <B> map(f: (A) -> B): Maybe<B> = when (this) { is Just -> Just(f(value)) // 값이 있으면 변환 is None -> None // 없으면 그대로 None } }반응형'Development > Architecture' 카테고리의 다른 글
아키텍처 퀀텀 (Architecture Quantum) (0) 2026.02.17 [Domain Driven Design] 도메인 모델 정리 (1) 2021.05.25 [Clean Architecture] 컴포넌트 결합 (0) 2020.06.29 [Clean Architecture] 컴포넌트 응집도 (0) 2020.06.27 [Clean Architecture] 3부. 설계 원칙 (0) 2020.06.11