Functors   

What if I told you that you already use functors in Kotlin? Surprised? Let's have a look at the following code:

fun main(args: Array<String>) {
listOf(1, 2, 3)
.map { i -> i * 2 }
.map(Int::toString)
.forEach(::println)
}

The List<T> class has a function, map(transform: (T) -> R): List<R>.  Where does the name map come from? It came from category theory. What we do when we transform from Int to String, is we map from the Int category to the String category. In the same sense, in our example, we transform from List<Int> to List<Int> (not that exciting), and then from List<Int> to List<String>. We didn't change the external type, just the internal value.

And that is a functor. A functor is a type that defines a way to transform or to map its content. You can find different definitions of a functor, more or less academic; but in principle, all point to the same direction.

Let's define a generic interface for a functor type:

interface Functor<C<_>> { //Invalid Kotlin code
fun <A,B> map(ca: C<A>, transform: (A) -> B): C<B>
}

And, it doesn't compile because Kotlin doesn't support higher-kinded types.

You'll find more information on higher-kinded types for Kotlin, including alternatives and the future of Kotlin, in Chapter 13, Arrow Types.

In languages that support higher-kinded types, such as Scala and Haskell, it is possible to define a Functor type, for example, the Scala cats functor:

trait Functor[F[_]] extends Invariant[F] { self =>
def map[A, B](fa: F[A])(f: A => B): F[B]

//More code here

In Kotlin, we don't have those features, but we can simulate them by convention. If a type has a function or an extension function, then map is a functor (this is called structural typing, defining a type by its structure rather than its hierarchy).

We can have a simple Option type:

sealed class Option<out T> {
object None : Option<Nothing>() {
override fun toString() = "None"
}

data class Some<out T>(val value: T) : Option<T>()

companion object
}

Then, you can define a map function for it:

fun <T, R> Option<T>.map(transform: (T) -> R): Option<R> = when (this) {
Option.None -> Option.None
is Option.Some -> Option.Some(transform(value))
}

And use it in the following way:

fun main(args: Array<String>) {
println(Option.Some("Kotlin")
.map(String::toUpperCase)) //Some(value=KOTLIN)
}

Now, an Option value will behave differently for Some and None:

fun main(args: Array<String>) {
println(Option.Some("Kotlin").map(String::toUpperCase)) //Some(value=KOTLIN)
println(Option.None.map(String::toUpperCase)) //None
}

Extension functions are so flexible that we can write a map function for a function type, (A) -> B, therefore, transforming functions into functors:

fun <A, B, C> ((A) -> B).map(transform: (B) -> C): (A) -> C = { t -> transform(this(t)) }

What we are changing here is the return type from B to C by applying the parameter function, transform: (B) -> C to the result of the function (A) -> B itself:

fun main(args: Array<String>) {
val add3AndMultiplyBy2: (Int) -> Int = { i: Int -> i + 3 }.map { j -> j * 2 }
println(add3AndMultiplyBy2(0)) //6
println(add3AndMultiplyBy2(1)) //8
println(add3AndMultiplyBy2(2)) //10
}

If you have experience in other functional programming languages, recognize this behavior as forward function composition (more on function composition in Chapter 12, Getting Started with Arrow).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.218.226