This is a translation of Functors, Applicatives, And Monads In Pictures from Haskell into Kotlin.
Actually this is a translation of another translation from Haskell to Swift.
I read through the original post and I found it really interesting for learning new concepts of FP, so I decided to do an additional translation (having also some fun in the way).
I also wanted to see how far can Kotlin get compared to Swift :)
If you enjoy this post be sure to say thanks to the author of the original version: Aditya Bhargava, @_egonschiele on Twitter.
Here’s a simple value:
And we know how to apply a function to this value:
Simple enough. Lets extend this by saying that any value can be in a context. For now you can think of a context as a box that you can put a value in:
Now when you apply a function to this value, you’ll get different results depending on the context. This is the idea that Functors, Applicatives, Monads, Arrows etc are all based on. The Option
data type defines two related contexts:
Note: the pictures use Maybe (Just | None) from Haskell, which correspond to a custom Kotlin’s Option (Some | None) implementation.
sealed class Option<out A> {object None : Option<Nothing>()data class Some<out A>(val value: A) : Option<A>() }
In a second we will see how function application is different when something is a Some(T)
versus a None
. First let’s talk about Functors!
When a value is wrapped in a context, you can’t apply a normal function to it:
This is where map
comes in (fmap
in Haskell). map
is from the street, map
is hip to contexts. map
knows how to apply functions to values that are wrapped in a context. For example, suppose you want to apply a function that adds 3 to Some(2)
. Use map
:
fun sumThree(n: Int) = n + 3 Option.Some(2).map(::sumThree) // => Some(5)
or with a simple syntax using an anonymous lambda:
Option.Some(2).map
Bam! map
shows us how it’s done! But how does map
know how to apply the function?
A Functor is any type that defines how map
(fmap
in Haskell) applies to it. Here’s how map
works:
So we can do this:
Option.Some(2).map
And map
magically applies this function, because Option
is a Functor. It specifies how map
applies to Some
s and None
s:
inline fun <B> map(f: (A) -> B): Option<B> = when (this) {is None -> thisis Some -> Some(f(value))}
Here’s what is happening behind the scenes when we write Option.Some(2).map { it + 3 }
:
So then you’re like, alright map
, please apply { it + 3 }
to a None
?
Option.None.map { it + 3 }// => None
Well, there's a gotcha here since the code above doesn't compile. Why? Well because in this case None doesn't have a proper type, so you cannot do a plus with type Nothing
. But it should be fine because you normally won't write that code but something like:
val option: Option<Int> = someCallThatMightReturnNone()option.map { it + 3 }// => None
Like Morpheus in the Matrix, map
knows just what to do; you start with None
, and you end up with None
! map
is zen. Now it makes sense why the Option
type exists. For example, here’s how you work with a database record in a language without Option
:
val post = Post.findByID(1)return post?.title
But in Kotlin using the Option
functor:
findPost(1).map(::getPostTitle)
If findPost(1)
returns a post, we will get the title with getPostTitle
. If it returns None
, we will return None
!
We can even define map
as an infix function for (<$>
in Haskell), and do this instead:
inline infix fun <B> map(f: (A) -> B): Option<B> { ... }
findPost(1) map ::getPostTitle
Note: we have to use just
_map_
because_<$>_
wouldn't compile. Another option would be to override a common operator like_/_
or_*_
Here’s another example: what happens when you apply a function to an array?
Arrays are functors too*!
*Basically Kotlin provides an extension function to all iterables in the form:
inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {..}
Okay, okay, one last example: what happens when you apply a function to another function?
{ a: Int -> a + 2 } map { a: Int -> a + 3 } // => ???
Here's a function:
Here’s a function applied to another function:
The result is just another function!
typealias IntFunction = (Int) -> Int
So functions can be Functors too! When you use map
on a function, you’re just doing function composition!
Want more? Go try applicatives in the second part!
Kotlin Functors, Applicatives, And Monads in Pictures. Part 2/3_This is a translation of Functors, Applicatives, And Monads In Pictures from Haskell into Kotlin._medium.com