일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- hdfs
- DDD
- Spring
- 제주
- 도메인주도설계
- Gradle
- intellij
- Storm
- 인텔리J
- docker
- Clean Code
- Java
- Hbase
- Spring Batch
- elastic search
- scala
- apache storm
- 스프링 배치
- design pattern
- hibernate
- nginx
- Spring Boot
- Angular2
- elasticsearch
- Linux
- spark
- 엘라스틱서치
- Spring XD
- hadoop
- SBT
Archives
- Today
- Total
욱'S 노트
모나드 소개/Introduction of Monad (Kotlin 함수형 프로그래밍 #8) 본문
Methdology/Functional Programming
모나드 소개/Introduction of Monad (Kotlin 함수형 프로그래밍 #8)
devsun 2025. 1. 22. 10:56반응형
일단 모나드에 대한 용어에 신경쓰지 말고 시작해보자.
모나드가 없을 때
간단한 Either를 하나 작성해보자.
sealed interface Either<out A, out B> {
data class Left<A>(val error: A) : Either<A, Nothing>
data class Right<B>(val value: B) : Either<Nothing, B>
}
계좌의 입출금 문제를 생각해보자
sealed interface Error {
data object NegativeAmount : Error
}
data class Account private constructor(val balance: BigDecimal) {
companion object {
fun create(initial: BigDecimal): Either<Error, Account> {
return if (initial < BigDecimal.ZERO) Left(NegativeAmount)
else Right(Account(initial))
}
}
fun deposit(amount: BigDecimal): Either<Error, Account> {
return if (amount < BigDecimal.ZERO) Left(NegativeAmount)
else Right(Account(balance + amount))
}
fun withdraw(amount: BigDecimal): Either<Error, Account> {
return if (amount < BigDecimal.ZERO) Left(NegativeAmount)
else Right(Account(balance - amount))
}
}
일반적으로 입출금을 수행하는 코드를 작성해보면 다음과 같다.
val accountResult = Account.create(100.toBigDecimal())
val depositResult = when (accountResult) {
is Right -> accountResult.value.deposit(100.toBigDecimal())
is Left -> Left(accountResult.error)
}
val withdrawResult = when (depositResult) {
is Right -> depositResult.value.withdraw(50.toBigDecimal())
is Left -> Left(depositResult.error)
}
withdrawResult shouldBe Right(50.toBigDecimal())
모나드 적용
모나드의 인터페이스는 다음과 같다. 모나드의 특징은 합성 가능하다는 것이다. 그래서 flatMap 연산이 가능하다.
interface Monad<A> {
fun <B> flatMap(f: (A) -> Monad<B>): Monad<B>
fun <A> unit(a: A): Monad<A>
}
두가지 연산을 Either에 추가해보자.
sealed interface Either<out A, out B> {
data class Left<A>(val error: A) : Either<A, Nothing>
data class Right<B>(val value: B) : Either<Nothing, B>
fun <A, C> flatMap(fn: (B) -> Either<A, C>): Either<A, C> = when (this) {
is Right -> fn(this.value)
is Left -> this as Either<A, C>
}
fun <A, B> unit(b: B): Either<A, B> = Right(b)
}
위의 출금 연산을 다시 작성해보면 아래와 같다.
Account.create(100.toBigDecimal())
.flatMap { it.deposit(50.toBigDecimal()) }
.flatMap { it.withdraw(30.toBigDecimal()) } shouldBe Right(50.toBigDecimal())
모나드로 할 수 있는 것
모나드의 가장 좋은 점은 합성 가능하다는 것이다. 합성 가능하다는 특성으로 아래와 같은 일들을 수행할 수 있다.
- Nullability: Maybe/Option monad
- Error Handling: Either monad
- DI (Dependency Injection): Reader monad
- Logging: Writer monad
- Side Effects : IO monad
- State handling: State monad
- Collections: List monad
- Async Handling : Future monad
- Parallel Processing: Par Monad
State Monad
data class State<S, out A>(val run: (S) -> Pair<A, S>) {
fun <B> flatMap(f: (A) -> State<S, B>): State<S, B> =
State { s0 ->
val (a, s1) = run(s0)
f(a).run(s1)
}
fun <B> map(f: (A) -> B): State<S, B> = flatMap { a -> State { s -> Pair(f(a), s) } }
}
// Usage example
data class CounterState(val count: Int)
fun incrementCounter(): State<CounterState, Unit> = State { state ->
Pair(Unit, state.copy(count = state.count + 1))
}
fun doubleCounter(): State<CounterState, Unit> = State { state ->
Pair(Unit, state.copy(count = state.count * 2))
}
val result: Pair<Unit, CounterState> = incrementCounter().flatMap { doubleCounter() }.run(CounterState(0))
Reader 모나드
typealias Reader<E, A> = (E) -> A
fun <E, A, B> Reader<E, A>.flatMap(transform: (A) -> Reader<E, B>): Reader<E, B> =
{ env -> transform(this(env))(env) }
fun <E, A, B> Reader<E, A>.map(transform: (A) -> B): Reader<E, B> =
{ env -> transform(this(env)) }
// Usage example
data class AppConfig(val apiUrl: String)
fun fetchUser(userId: String): Reader<AppConfig, String> =
{ config -> "User $userId fetched from ${config.apiUrl}" }
fun logUser(user: String): Reader<AppConfig, Unit> =
{ config -> println("Logging user: $user to ${config.apiUrl}") }
val program: Reader<AppConfig, Unit> =
fetchUser("123").flatMap { user ->
logUser(user)
}
// Running the program
val config = AppConfig("https://api.example.com")
program(config)
Future Monad
suspend fun <T> asyncOperation(): T = suspendCoroutine { continuation ->
// Simulating an async operation
GlobalScope.launch {
delay(1000) // Simulating a delay
val result = /* Perform some computation */
continuation.resume(result)
}
}
fun <T, R> Deferred<T>.flatMap(transform: (T) -> Deferred<R>): Deferred<R> =
GlobalScope.async { transform(await()).await() }
fun <T, R> Deferred<T>.map(transform: (T) -> R): Deferred<R> =
GlobalScope.async { transform(await()) }
// Usage example
val result: Deferred<Int> = asyncOperation<Int>().flatMap { value ->
asyncOperation<Int>().map { value + it }
}
runBlocking { println(result.await()) }
Par Monad
@extension
interface ParMonad : Monad<ForPar> {
override fun <A> unit(a: A): ParOf<A> = Par.unit(a)
override fun <A, B> flatMap(
fa: ParOf<A>,
f: (A) -> ParOf<B>
): ParOf<B> =
fa.fix().flatMap { a -> f(a).fix() }
}
도대체 모나드란 무엇인가?
모나드는 결합 법칙과 항등원 법칙을 만족하는 모나드적인 콤비네티어의 최소 집합중 하나를 구현한 것이다. 모나드의 인스턴스는 세가지 집합 중 어느 하나를 제공해야만 한다.
- flatMap, unit
- compose, unit
- map, join, unit
모나드 법칙
왼쪽 항등법칙(Left Identity)
- 모나드가 값을 감싸는 방식이 함수를 직접 적용하는 것과 동일하게 작동해야 한다.
pure(x).flatMap(f) == f(x)
Option(1).flatMap(f) == Option(f(1))
None.flatMap(f) == None
오른쪽 항등법칙(Right Identity)
- 모나드에 pure를 적용했을때 결과가 모나드 자신과 동일해야 된다.
m.flatMap(pure) == m
Option(1).flatMap(pure) == Option(1)
None.flatMap(pure) == None
결합법칙
- 모나드 연산의 순서가 결과에 영향을 미치치 않아야 한다.
(m.flatMap(f)).flatMap(g) == m.flatMap { x → f(x).flatMap(g) }
정리
- 모나드는 항등원 법칙과 결합법칙을 만족하여 합성 가능하다.
- 모나드는 합성가능하다.
- 모나드의 합성가능함으로써 코드의 안정성과 가독성을 높이고, 동시성과 병렬성 처리에 용이하다.
참고자료: https://www.dak.so/eb0005f6-5175-41ca-942a-56868f55f21c
반응형
'Methdology > Functional Programming' 카테고리의 다른 글
모노이드/Monoid (Kotlin 함수형 프로그래밍 #9) (3) | 2025.01.23 |
---|---|
컴파일타임 디펜던시 인젝션/Compile Time Dependency Injection (Kotlin 함수형프로그래밍 #8) (4) | 2025.01.21 |
도메인 모델링/Domain Modeling (Kotlin 함수형 프로그래밍 #7) (2) | 2025.01.20 |
외부효과와 입출력/Effects and IO (Kotlin 함수형프로그래밍 #6) (0) | 2025.01.17 |
불변 컬렉션/Immutable Collections (Kotlin 함수형 프로그래밍 #5) (4) | 2025.01.16 |