paint-brush
Functor Applicative and Monadsby@vijay-aneryae
849 reads
849 reads

Functor Applicative and Monads

by Vijay AnerayeDecember 29th, 2019
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

I am putting my understanding about functor,Applicative and Monad in Kotlin. I am using Kotlin to explain with examples with examples. A functor is a data structure which acts like a container holding a generic type. Applicative provides abstraction for how to apply a function that takes multiple arguments over multiple values. Functor is useful because they let us use map with collections, replacing for loops. Because Functormap returns another Functor with the result of the function passed to map I can chain multiple map functions together.

Coin Mentioned

Mention Thumbnail
featured image - Functor Applicative and Monads
Vijay Aneraye HackerNoon profile picture

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

What is functor

Functor can be defines in may ways as below

  • A functor is simply something that can be mapped over.
  • A functor is a data structure which acts like a container holding a generic type

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 .

Why Are Functors Useful?

  • Because we want to reuse code.
  • Functor generalizes how to map a function from one value to another.
  • Functors are useful because they let us use map with collections, replacing for loops
  • Chaining: Because Functor.map returns another Functor with the result of the function passed to map I can chain multiple map functions together

Applicative

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)

Monads

Before we start with Monads, it is important to understand function composition in kotlin

Function Composition

  • Function composition is a technique to build functions using existing functions
  • Function Composition takes the result of invoking the right-hand function as the parameter for the left-hand function.
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
  1. To solve this issue, we are going to breakdown problem in three function — Splitting — Parsing — Division
  2. We usually do this using composition as below
  3. 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
    • Above use case for composition is not perfect , the reason because things can go wrong, like split function may return exception because these is no comma in between two digits, parse function may fail may be there is no number and eventually the result can fail
    • For any exceptions, the program can be partial , then what we can do
    • One way to do this is we compose these functions in a way to return “Embellished” results, That mean we can wrap the result in class (Functor) and that is where the term Monad comes

    Monads

    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!