kotlin이나 scala를 사용한다면 굉장히 익숙한 함수로 array나 list, map 등의 class에 구현되어 있는 map 함수가 있습니다.
이 map 함수를 잘 몰라도 대강 내부 원소를 A 에서 B로 변환해주는구나 정도로 이해하고 사용해도 상관 없습니다만 이게 어떤 함수인지 알아야 더 잘 사용할 수 있으니 최대한 도움이 될 정도로 정리해보겠습니다.
코드는 kotlin으로 작성할 예정이고 어떤 피드백이든 환영합니다.
Functor와 map
범주론에서 함자(函子, 영어: functor)는 두 범주 사이의 함수에 해당하는 구조로, 대상을 대상으로, 사상을 사상으로 대응시킨다. 함자는 작은 범주의 범주의 사상으로 볼 수 있다.
- wikipedia
map은 원래 카테고리 이론의 Functor 에서 유래된 함수입니다.
간단히 interface로 나타내보면 대략적으로는 이런 코드가 됩니다.
interface Functor<A> {
fun <B> map(f: (A) -> B): Functor<B>
}
자주 보게되는 map을 지원해주는 구현체로 Option, Either, List 같은 객체가 존재하고, scala에서는 기본 Standard Library로 지원해주지만 Kotlin, Java에서는 지원해주지 않으므로 Arrow.kt나 Vavr과 같은 함수형 라이브러리를 추가해야 합니다.
// Option
sealed class Option<A> {
data object None : Option<Nothing>()
data class Some(val value: A) : Option<A>
}
// list
sealed class List<A> {
data object Nil : List<Nothing>()
data class Node<A>(val head: A, val tail: List<A>) : List<A>
}
// Try
sealed class Try<A> {
data class Success<A>(val value: A) : Try<A>()
data class Failure<A>(val ex: Throwable) : Try<A>()
}
// 사용 예: Option
// 결과는 None 입니다
Option<String>(null).map { s -> s.upperCase() }
// 결과는 Some("A") 입니다
Option<String>("a").map { s -> s.upperCase() }
Functor 법칙
Functor가 수학적 이론이라 만족해야 하는 법칙이 있는데 기본적으로 아래의 두 가지 법칙을 만족해야 합니다.
identity
모든 원소에 identity 함수를 매핑하면 그 자신이 되어야 합니다.
// fun <A> identity(a: A) = a
fa.map(::identity) == fa
composition
모든 원소에 순차적으로 두 함수를 적용한 값은 두 함수를 조합해서 한 번에 적용한 값과 같아야 합니다.
fa.map(f).map(g) == fa.map(f andThen g)
Functor를 사용하는 이유
최신 프로그래밍 언어들이 이렇게 복잡한 수학적 이론들을 만족하는 클래스를 왜 만들어서 사용하냐고 하면 분명한 장점이 있어서 그렇습니다.
- 수학적인 이론을 만족하게 구현하였으니 그 법칙만으로 클래스의 속성을 유추할 수 있습니다
- 법칙을 만족하면 내부의 상세 구현을 알지 못해도 사용할 수 있습니다
- identity랑 composition을 통해서 map(x)가 x의 구조를 보존해준다는 사실을 유추할 수 있습니다. 이 두 법칙을 만족하면 map은 side effect가 없는 컨테이너 내부 값의 변화라는 사실을 알 수 있습니다 (사실 수학적인 함수는 원래 side effect가 없습니다)
- map을 사용하면 법칙이 정의된 컨텍스트 내에서 법칙을 위반하지 않는 구조 변환을 지원해줍니다
- Future에서 map을 통해 비동기 연산을 지원하면서, 값이 없어도 있는 것처럼 동기 코드를 짤 수 있습니다
- Option에서 map을 통해 값의 유무와 상관없이 코드를 짤 수 있습니다
우리가 자주 사용하는 List, Future, Result 등의 map도 위의 속성을 만족하니 functor라고 이해할 수 있습니다.
정리
map은 functor의 속성을 만족하는 함수로, identity와 composition 법칙을 만족해야 합니다.
map을 사용하면 side effect 없이 컨테이너의 구조를 변화시키지 않고 내부의 값을 변화시킬 수 있습니다.