Functor and Monad in Functional Programming
Reactive
RxJava was designed and built on top of very fundamental concepts like functors , monoids and monads. RxJava, working on top of similarly imperative language, the library has its roots in functional programming
Reactive 에서도 그렇고, Java8 이후로 우리는 Functor 와 Monad 라는 개념을 계속 사용하고 있었다.
Functor
Functor 는 값(value)을 포함 하는 컨테이너(또는 객체)를 나타내는 인터페이스 또는 타입 클래스이다.
Functor in Java:
import java.util.function.Function;
interface Functor<T> {
<R> Functor<R> map(Function<T, R> f);
}
Functor in Kotlin:
class Functor<T>(private val value: T) {
fun <R> map(f: (T) -> R): Functor<R> = Functor(f(this.value))
}
여기서 map 은 우리가 흔히 사용하던 map 이 맞다. 함수를 받아 값을 변형하는 함수 이다. Functor 에서는 map 과 같이 함수를 받아 값을 변형하는 함수가 존재한다.
Formally, a functor is a type class or a concept that satisfies the following rules:
- Identity: Mapping an identity function over a functor should not change the functor. In other words, if we map the identity function id over a functor F, it should return the same functor F unchanged.
- F.map(id) == F
- Composition: If we have two functions f: A -> B and g: B -> C, and we map them over a functor F, it should be the same as first mapping f over F and then mapping g over the result.
- F.map(g.compose(f)) == F.map(f).map(g)
Kotlin 에서 Result 타입은 Functor 이다. 값 객체라는 것을 알 수 있다. 즉, Value Semantics 를 따른다.
@JvmInline
public value class Result<out T> @PublishedApi internal constructor(
@PublishedApi
internal val value: Any?
) : Serializable {
// ...
@InlineOnly
@SinceKotlin("1.3")
public inline fun <R> runCatching(block: () -> R): Result<R> {
return try {
Result.success(block())
} catch (e: Throwable) {
Result.failure(e)
}
}
@InlineOnly
@SinceKotlin("1.3")
public inline fun <R, T> Result<T>.map(transform: (value: T) -> R): Result<R> {
contract {
callsInPlace(transform, InvocationKind.AT_MOST_ONCE)
}
return when {
isSuccess -> Result.success(transform(value as T))
else -> Result(value)
}
}
}
Monad 로 넘어가기전에 Monad 가 Functor 의 어떤 문제를 해결하는지를 알아야 한다.
우리가 흔히 map 을 사용하다보면 중첩(nested) 문제를 겪게 된다.
Nested Functors
sealed class Option<out T>
data class Some<out T>(val value: T) : Option<T>()
object None : Option<Nothing>()
fun main() {
val option1: Option<Int> = Some(5)
val option2: Option<Option<Int>> = Some(option1)
val mappedOption: Option<Option<Int>> = option2.map { innerOption -> innerOption.map { it * 2 } }
// using when twice
when (mappedOption) {
is Some -> {
when (val innerOption = mappedOption.value) {
is Some -> println(innerOption.value) // Output: 10
is None -> println("Inner Option is None")
}
}
is None -> println("Outer Option is None")
}
}
이러한 Nested Functors 문제를 해결해주는 것이 Monad 이다.
Monad
Monad 를 이해하기 가장 쉬운 방법은 flatMap 을 떠올리는 것이다. flatMap 은 반환 결과를 값으로 그대로 사용한다. 즉, flatMap 을 사용하여 중첩된 컨테이너를 평면화하고, 여러 단계의 변형을 순차적으로 적용할 수 있다.
fun <V, R, E> Result<V, E>.flatMap(f: (V) -> Result<R, E>): Result<R, E> =
when (this) {
is Result.Success -> f(this.value)
is Result.Failure -> this
}