This is a translation of Functors, Applicatives, And Monads In Pictures from Haskell.
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: @_egonschiele
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’sOption (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!
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)
Bam! map
shows us how it’s done! But how does map
know how to apply the function?
Just what is a Functor, really?
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 { it + 3 }
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 -> this
is Some -> Some(f(value))
}
Here’s what is happening behind the scenes when we write the above
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!
Well, that’s it for today, I hope you got the idea about what’s a Functor. Since the original post was pretty long, I’ll continue in the next series with Applicatives. Now go try write some Functors in Kotlin!
Wanna play around with the code? Take a look at https://github.com/aballano/FAM-Playground
Want more? Go try applicatives in the second part!
Kotlin Functors, Applicatives, And Monads in Pictures. Part 2/3
Hacker Noon is how hackers start their afternoons. We’re a part of the @AMI family. We are now accepting submissions and happy to discuss advertising & sponsorship opportunities.
If you enjoyed this story, we recommend reading our latest tech stories and trending tech stories. Until next time, don’t take the realities of the world for granted!
By Alberto Ballano on March 28, 2017.
Exported from Medium on March 3, 2023.