펑터(Functor)는 매핑할 수 있는 것(can be mapped over)이라는 행위를 선언한 타입 클래스를 말한다.
자세히 정리하면 펑터는 리스트 같은 컨테이너형 타입의 값을 꺼내서 입력받은 함수를 적용한 후, 함수의 결괏값을 컨테이너형 타입에 넣어서 반환하는 행위를 선언한 타입 클래스를 말한다.
펑터 자체는 추상화된 타입 클래스이기 때문에 컨테이너형 타입이 가진 구체적인 타입까지 포함해서 정의하지 않는다. (List<Int> 대신 List<T>)
오로지 한 개의 매개변수를 받는 타입생성자(type constructor)다.
interface Functor<out A> {
fun <B> fmap(f: (A) -> B): Functor<B>
}
class Functor f where
fmap : (a -> b) -> f a -> f b
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
sealed class Maybe<out A>: Functor<A> {
abstract override fun toString(): String
abstract override fun <B> fmap(f: (A) -> B): Maybe<B>
}
data class Just<out A>(val value: A): Maybe<A>() {
override fun toString(): String = "Just($value)"
override fun <B> fmap(f: (A) -> B): Maybe<B> = Just(f(value))
}
object Nothing: Maybe<kotlin.Nothing>() {
override fun toString(): String = "Nothing"
override fun <B> fmap(f: (kotlin.Nothing) -> B): Maybe<B> = Nothing
}
fun main() {
println(Just(10).fmap { it + 10 }) // Just(20)
println(Nothing.fmap { a: Int -> a + 10 }) // Nothing
}
펑터는 타입 생성자에서 컨테이너형 타입을 요구한다. 따라서 어떤 값을 담을 수 있는 타입은 항상 펑터로 만드는 것을 생각해 볼 수 있다.
펑터의 법칙을 모두 만족하면 map 함수를 호출했을 때 매핑하는 동작 외에 어떤 것도 하지 않는다는 것을 알 수 있다. 이러한 예측 가능성은 함수가 안정적으로 동작할 뿐만 아니라 더 추상적인 코드로 확장할 때도 도움이 된다.
코틀린으로 배우는 함수형 프로그래밍 7장