Para un mismo problema existe más de una solución aunque a veces existen barreras tecnicas que nos impiden abordarlas. Esta presentación pretende mostrar como, gracias a Kotlin, se nos abren otras opciones a la hora de abordar los problemas, centrandonos en nuestros problemas particulares y delegando a un segundo plano problemas recurrentes como la gestión de errores o la concurrencia.
4. Support High-Order Functions and Lambda Expressions
In mathematics and computer science, a higher-order function (also
functional, functional form or functor) is a function that does at least one of
the following:
● Takes one or more functions as arguments.
● Returns a function as its result.
● Is assignable to symbols.
5. fun example() {
val myList: List<Int> = listOf(12, 15, 23, 24, 34, 35, 45)
val myFirstFilteredList = myList.filter({ x -> x > 23 })
val mySecondFilteredList = myList.filter { x -> x > 23 }
val myThirdFilteredList = myList.filter { it > 23 }
val myFilter = getFilterGreaterThan(23)
val myFourthFilteredList = myList.filter(myFilter)
}
private fun getFilter(i: Int): (Int) -> Boolean = { x -> x > i }
private fun getFilterAlternative(i: Int): (Int) -> Boolean = { it > i }
6. Sealed Classes (As Java Enum but much more powerful)
Sealed classes are used for representing restricted class hierarchies, when a
value can have one of the types from a limited set, but cannot have any other
type.
sealed class Expression {
class Const(val number: Double) : Expression()
class Sum(val e1: Expr, val e2: Expr) : Expression()
object NotANumber : Expression()
}
7. Sealed Classes (As Java Enum but much more powerful)
The key benefit of using sealed classes comes into play when you use them
in a ‘when expression (as statement)’.
fun eval(expr: Expression): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// the 'else' clause is not required because we've covered all the cases
}
8. Type Inference
In Kotlin, you do not have to specify the type of each variable or return value
explicitly.
// Type is String
val name = "JMPergar"
// Type is Int
var age = 33
// Type is ArrayList
val arrayList = arrayListOf("Kotlin", "Scala")
// Return type is Boolean
private fun getBoolean(x: Int) = x > 12
9. Exceptions
● All exception classes in Kotlin are descendants of the class Throwable.
● Kotlin does not have checked exceptions.
11. Functional Programming
It’s a programming paradigm that follow this rules:
● Functions are First-class citizen.
● Don’t break referential transparency.
● Avoid mutable data.
"Eliminating side effects can make it much easier to understand
and predict the behavior of a program."
12. Monads
A Monad is just a monoid in the category of
endofunctors.
What’s the big deal?
13. Monads
It’s more easy:
● A Monad is a box for a value or set of values of type T1, T2, T3…
● A box with superpowers.
● And this box support two methods:
■ map for transformations.
■ flatMap for sequentiality.
14. fun <A, B> Monad<A>.map(f: (A) -> B): Monad<B>
val monadStr: Monad<String>
val result: Monad<Int> = monadStr.map { str -> str.length() }
Map method
15. fun <A, B> Monad<A>.map(f: (A) -> B): Monad<B>
val result1: Monad<String>
val result2: Monad<String>
val result: _________________ =
result1.map {
res1 -> result2.map {
res2 -> res1.length() + res2.length()
}
}
Map method
16. fun <A, B> Monad<A>.map(f: (A) -> B): Monad<B>
val result1: Monad<String>
val result2: Monad<String>
val result: Monad<Monad<Int>> =
result1.map {
res1 -> result2.map {
res2 -> res1.length() + res2.length()
}
}
Map method
17. fun <A, B> Monad<A>.flatMap(f: (A) -> Monad<B>): Monad<B>
val result1: Monad<String>
val result2: Monad<String>
val result: __________ =
result1.flatMap {
res1 -> result2.map { res2 -> res1.length() + res2.length() }
}
FlatMap method
18. fun <A, B> Monad<A>.flatMap(f: (A) -> Monad<B>): Monad<B>
val result1: Monad<String>
val result2: Monad<String>
val result: Monad<Int> =
result1.flatMap {
res1 -> result2.map { res2 -> res1.length() + res2.length() }
}
FlatMap method
20. Old way: Exceptions
● Break referential transparency.
○ Don’t know how to recover from errors (unchecked).
○ Compiler optimization aren’t possible.
○ Break the flow (like GOTO).
● Not valid with asynchrony. Callbacks are needed to move exceptions between
threads.
● Slow (due to fillInStackTrace ) and not constant.
● Checked (info about errors in the signature, but all the other problems persist).
● Boilerplate in the management.
23. New way: Monads
All types have the same combinators for the
happy case.
Thanks to this and the type inference our
codebase will be much more open to change.
24. Either<L, R>
Represents a value of one of two possible types (a disjoint union.)
An instance of Either is either an instance of [Left] or [Right].
val myEither: Either<Error, String>
sealed class Errors {
class NetworkError : Error()
class ServerError : Error()
}
With this and methods like map and flatMap we can develop our flow under
the assumption that everything will be ok.
25. map and flatMap methods
These are our calls with monadic style.
val result1: Either<Error, String> = firstCall()
val result2: Either<Error, String> = secondCall()
val result3: Either<Error, String> = thirdCall()
val result: Either<Error, T> =
result1.flatMap {
str1 -> result2.flatMap {
str2 -> result3.map {
str3 -> str1.length() + str2.length() + str3.length();
}
}
}
But this is still too much verbose. And what about errors?
26. For Comprehensions
Scala
for {
res1 <- result1
res2 <- result2
res3 <- result3
} yield (res1 + res2 + res3)
Kotlin with Katz
val result = EitherMonad<String>().binding {
val res1 = !result1
val res2 = result2.bind()
val res3 = bind { result3 }
yields(res1 + res2 + res3)
}
28. Apply side effects
val result = EitherMonad<String>().binding {
val res1 = bind { result1 }
val res2 = bind { result2 }
yields(res1 + res2)
}
processResult(result)
private fun processResult(result: Either<Error, String>): Any? = when (result) {
is Either.Left -> result.swap().map { manageErrors(it) }
is Either.Right -> result.map { manageResult(it) }
}
Other alternatives are use methods like ‘fold’, ‘recover’ (for Try) or similar.
Each implementation has its own methods and are usually based on ‘fold’.
33. Old way
Promises Libraries
● Clean and semantic but not standard.
RxJava
● Taking a sledgehammer to crack a nut.
● Many not needed features in languages
like kotlin.
● Is your problem reactive?
Java Future
● Java 7: Future (Bloquer).
● Java 8: CompletableFuture (Not
bloquer but not monadic).
Threads
● More difficult to be efficient.
● Easy to have errors.
● Callback hell.
35. New way: Future<T> (with Scala)
We can apply ‘for comprehension’ or all monadic combinators too.
val finalResult = for {
res1 <- myResultFuture
res2 <- myOtherResultFuture
} yield (res1 + res2)
finalResult.onComplete {
case Success(result) => processResult(result)
case Failure(t) => println("Error: " + t.getMessage)
}
36. Conclusions
● Monads provide us a standard way of deal with problems outside our domain.
● Monads abstract and solve standard problems.
● Monads Driven Development make our code more open to change.
Read, analyze, think, but don’t fear the change.
Maybe in the change is the solution.
37. Links and Resources
● [ESP] Lleva tus use cases al siguiente nivel con Monads y Kotlin
https://jmpergar.com/tech/2017/03/02/lleva-tus-use-cases-al-siguiente-nivel-con-monads.html
● Kotlin Functors, Applicatives, And Monads in Pictures.
https://hackernoon.com/kotlin-functors-applicatives-and-monads-in-pictures-part-1-3-c47a1b1ce25
1
● [ESP] Functional Error Handling https://www.youtube.com/watch?v=cnOA7HdNUR4
● [ESP] Arquitecturas funcionales: más allá de las lambdas https://youtu.be/CT58M6CH0m4
● Antonio Leiva https://antonioleiva.com/category/blog/
● [ESP] DevExperto https://devexperto.com/blog/
● [Book] Kotlin for Android Developers
https://www.amazon.com/Kotlin-Android-Developers-Learn-developing/dp/1530075610
● [Lib] Katz https://github.com/FineCinnamon/Katz
● [Lib] Funktionale https://github.com/MarioAriasC/funKTionale
● [Lib] FutureK https://github.com/JMPergar/FutureK