I am putting my understanding about functor,Applicative and Monad after spending few days to find out what monad is . Here is what I found. I am using Kotlin to explain with examples
Functor can be defines in may ways as below
Lets define the data type or container to hold value
class Functor<T> {
object None : Functor<Nothing>()
data class Some<out T>(val value: T) : Functor<T>()
}
While we introduced functors as containers holding a value, sometimes
those containers can have interesting properties. For this reason,
functors are often described as “values in a context”.
When a value is wrapped in a context, you can’t apply a normal function to
it, and this is where map function comes in to apply normal function to
Wrapped Value
sealed class Functor<out A> {
object None : Functor<Nothing>()
data class Some<out A>(val value: A) : Functor<A>()
inline infix fun <B> map(f: (A) -> B): Functor<B> = when (this) {
is None -> this
is Some -> Some(f(value))
}
companion object {
fun <A> some(value: A): Functor<A> = Some(value)
}
}
Lets apply function on the wrapped value
fun inc(value: Int): Int = value + 1
val increment = Functor(3).map {::inc} //OR
val increment = Functor(3).map {it + 1}
So this is functor, the container with map function .
Functor can only map a function which takes one argument. If we have a function that takes multiple arguments, we need Applicative.
Applicative provides abstraction for how to apply a function that takes multiple arguments over multiple values.
In Applicative, We can define an apply function for every type supporting Applicative, which knows how to apply a function wrapped in the context of the type to a value wrapped in the same context:
sealed class Functor<out A> {
object None : Functor<Nothing>()
data class Some<out A>(val value: A) : Functor<A>()
inline infix fun <B> map(f: (A) -> B): Functor<B> = when (this) {
is None -> this
is Some -> Some(f(value))
}
companion object {
fun <A> some(value: A): Functor<A> = Some(value)
}
infix fun <A, B> Functor<(A) -> B>.apply(f: Functor<A>): Functor<B> =
when (this) {
is None -> None
is Some -> f.map(this.value)
}
}
If you look carefully you will see that our operator only works in this specific order: Option(function) apply Option(value)
Examples 1 : Apply a function that takes two arguments to two wrapped values
fun curriedAddition(a: Int) = { b: Int ->
a + b
}
Some(3) map ::curriedAddition map Some(2) // => COMPILER ERROR// Use applicative
Some(3) map ::curriedAddition apply Some(2)
Apply triple product function:
fun tripleProduct(a: Int, b: Int, c: Int) = a * b * c
fun <A, B, C, D> curry(f: (A, B, C) -> D): (A) -> (B) -> (C) -> D = { a -> { b -> { c -> f(a, b, c) } } }
Some(3) map curry(::tripleProduct) apply Some(5) apply Some(4)
// => Some(60)
Before we start with Monads, it is important to understand function composition in kotlin
Take a Example of adding one and multiplying 3 to given number
val add : (Int) -> Int = {x -> x + 1 }
val mult : (Int) -> Int = { y -> y * 3}fun <A,B,C>composeF(f: (B) -> C, g: (A) ->B) {
return { x -> f(g(x)) }
}
val addOneThenMul3 = composeF(::mul3, ::add1)
print(addOneThenMul3(2)) // output 9
Take another Example , you will get the input as String of two digit separated by comma, you have to divide first digit by second digit . For Input= "126,3" , output should be output= 126/3 = 4
val splitString : (String) -> Pair<Stirng,String> = {
s -> s.split(",").first() to s.split(",").last()
}
val parseToDouble : (Pair<String,String>) -> Pair<Double,Double> = {d -> d.first.toDouble() to d.second.toDouble()}
val division : (Pair<Double,Double>) -> Double = {d -> d.first/d.second}f
un <A,B,C>composeF(f: (B) -> C, g: (A) ->B) {
return { x -> f(g(x)) }
}
val result = composeF(composeF(division,parseToDouble),splitString)
print(result("126,3")) // 42
Monads are the mechanism which makes automatic composition of special kids of functions
In another word, Monad is minimal amount of structure needed to overload functional composition in a way to perform extra computation on the intermediate value
Understand Monad with above use case of split, parse divide
To solve above above composition problem , we have to wrap the result in context of each function, so lets do it
To wrap value we need Functor
sealed class Result<out T> {
object None : Result<Nothing>()
data class Some<out T>(val value: T) : Result<T>()
}
And modify functions to return wrap result
val split : (String) -> Result<Pair<String,String>> = { s ->
val listString = s.split(",")
Some(listString.first() to listString.last())
}
val parse : (Pair<String,String>) -> Result<Pair<Double,Double>> = {pair -> Some(pair.first.toDouble() to pair.second.toDouble()) }
val divide : (Pair<Double,Double>) -> Result<Double> = {pair -> Some(pair.first.div(pair.second)) }
Here we have modified functions to returns a wrapped value,
We have to apply split function to parse function and then to divide function.
Since the return type of each function is wrapped value and input parameter of each function is accepting pure values, we can't easily pass these function returning wrapped value to other functions .Here is monad come in picture,
Monads apply a function that returns a wrapped value to a wrapped value using flatMap function
So flatMap function looks like
sealed class Result<out T> {
object None : Result<Nothing>()
data class Some<out T>(val value: T) : Result<T>()
inline fun <B> flatMap(f: (T) -> Result<B>) : Result<B> =
when (this) {
is None -> this
is Some -> f(value)
}
}
Here we have written flatMap function on Functor
Now you can apply function returning wrapped value to wrapped value
val output = split("126,3").flatMap(parse).flatMap(divide)
println(output) // will print 42
So ins simple word, Modad is Functor that has flatMap
Here is my github link for this example
Thats it. I hope this article will help you understand idea of Functor and Monads and who they are comes together .
This is my first article here and I hope you have enjoyed it ;) I’ll be glad for any feedback!