First of all, if you didn't read the previous post, go do so, otherwise you might be missing some essential concepts!
Kotlin Functors, Applicatives, And Monads in Pictures. Part 1/3 (draft)_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…_medium.com
Applicatives take it to the next level. With an applicative, our values are wrapped in a context, just like Functors:
But our functions are wrapped in a context too!
Yeah. Let that sink in. Applicatives don’t kid around. Unlike Haskell, Kotlin doesn’t have yet a built-in way to deal with Applicatives. But it is very easy to add one! 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:
infix fun <A, B> Option<(A) -> B>.apply(f: Option<A>): Option<B> =when (this) {is Option.None -> Option.Noneis Option.Some -> f.map(this.value)}
infix inline fun <A, reified B> Array<(A) -> B>.apply(a: Array<A>) =Array(this.size * a.size) {this[it / a.size](a[it % a.size])}
If both this
and the function are Some
, then the function is applied to the unwrapped option, otherwise None
is returned.
For the Array
I'm using its constructor parameters to generate the array, although note that this wouldn't be the most performant choice for bigger arrays.
Note: this would be the
<*>
in Haskell, so again we could use a*
operator if needed.
i.e:
Some({ a: Int -> a + 3 }) apply Some(2) // => Some(5)
If you look carefully you will see that our operator only works in this specific order: Option(function) apply Option(value)
why? Because our extension function is defined in that order. Then, couldn’t I just make another extension function to work the other way around, like:
fun <A, B> Option<A>.apply
(o: Option<(A) -> B>) = {...}fun <A, B> Option<(A) -> B>.apply
(o: Option<A>) = {...}
Technically not. Since Kotlin produces same code as Java would, it has to deal with our friend the type erasure. So basically those 2 functions would loose the generic types at compile time (and become just Option
), making them equal and therefore invalid. But here’s a trick, we could use a so called dummyImplicit
(and you can read why here) so it would look like:
fun <A, B> Option<A>.apply(f: Option<(A) -> B>,dummyImplicit: Any? = null): Option<B> =when (this) {is Option.None -> Option.Noneis Option.Some -> f.map { it(value) }}
This allows us to do:
Some(2).apply(Some({ a: Int -> a + 3 }))// => Some(5)
But unfortunately it makes the infix impossible since infix functions only have one parameter :(
So, following our previous explanation, using apply can lead to some interesting situations. For example:
arrayOf<(Int) -> Int>({ it + 3 }, { it * 2 }) apply arrayOf(1, 2, 3) // => [ 4, 5, 6, 2, 4, 6 ]
Note: the original article now shows how Applicatives are more powerful than Functors in that they allow function application with multiple parameters. Again this is not feasible in vanilla Kotlin, but we can work around it by defining the function we want to handle in a curried way.
Here’s something you can do with Applicatives that you can’t do with Functors. How do you 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
Applicatives:
Some(3) map ::curriedAddition apply Some(2)// => Some(5)
Wait, what?? Let me break it down step by step:
Some(3) map ::curriedAddition// => Some({ 3 + b })
Some({ 3 + b }) apply Some(2)// => Some(5)
Applicative
pushes Functor
aside. “Big boys can use functions with any number of arguments,” it says. “Armed with map and apply, I can take any function that expects any number of unwrapped values. Then I pass it all wrapped values, and I get a wrapped value out! AHAHAHAHAH!”
fun curriedTimes(a: Int) = { b: Int -> a * b}
Some(3) map ::curriedTimes apply Some(5)// => Some(15)
But wait, what if we want to go deeper?
Suppose we have a triple product function:
fun tripleProduct(a: Int, b: Int, c: Int) = a * b * c
We want to do the same as before but we don't want to manually curry it, so we have a curry function, in this case for 3 params:
fun <A, B, C, D> curry(f: (A, B, C) -> D): (A) -> (B) -> (C) -> D = { a -> { b -> { c -> f(a, b, c) } } }
Thanks Ľuboš Mudrák for the suggestion!
Well, that's easy:
Some(3) map curry(::tripleProduct) apply Some(5) apply Some(4)// => Some(60)
The third and final part is already available"
Kotlin Functors, Applicatives, And Monads in Pictures. Part 3/3_This is a translation of Functors, Applicatives, And Monads In Pictures from Haskell into Kotlin._medium.com