Slides from my talk about Kotlin & Functional programming with Arrow which I gave at Sunny Tech 2018 (http://sunny-tech.io)
It showcases how Kotlin is a good fit for functional programming, thanks to Arrow.
2. /me !
• Backend engineer at Sigfox
• Mostly OOP by day (Java, Spring 😒) but FP enthusiast
• FP by night & evangelization at work
• Not FP expert
• @nhanmanu on Twitter
3. Scope
Functional Programming:
Using pure functions
Strongly typed
Using techniques found in Scala
Kotlin (v1.2.x):
Focus on JVM only
Idioms to build FP
machinery
Arrow (v0.7.2):
How it works
Overview of core
5. Kotlin ?
• Hype 🤩
• Language tasting like Java with lots of sugar 🍯
• Inspired by many existing languages
• Runs on JVM ( including Android), JS, Native
• Developed by Jetbrains
• First class citizen in Android, Spring, Reactor,…
6. Why FP in Kotlin on JVM ?
• When the team is too scared by Scala
• Idioms which make FP possible & enjoyable
• But concepts are missing :
need to emulate them
8. Functions
// Declaring a simple function
fun add(a: Int, b: Int): Int = a + b
// Or
val minus: (Int, Int) -> Int =
{ a, b -> a - b}
// With generics & high order !
fun <A, B, C> compose(f: (A) ->B,
g: (B) ->C ): (A) ->C =
{ a: A -> g(f(a))}
Function definition
Return typeParameters
9. Extension Functions
• Feature allowing to add functions to an exiting type
// Enriching Int :
fun Int.minus3(): Int = this - 3
// Works also with generics
fun <A, B, C> ((A, B) ->C).partial(a: A):
(B) ->C = {b: B -> this(a, b)}
• Using this syntax:
6.minus3()
val plus4: (Int) ->Int = ::add.partial(4)
11. Types: data classes
data class Message(val author: String,
val recipient: String,
val content: String)
declares fields which are not reassignable
12. object
• Allows to declare singletons:
object Bar
• companion object adds methods « to the type itself »
// Declaration
class Foo {
// ...
companion object {
fun bar(): Int = TODO()
}
}
// Use
Foo.bar()
14. Types: Option
sealed class Option<out T>
data class Some<out T>(val t: T): Option<T>()
object None: Option<Nothing>()
// Usage:
val a: Option<Int> = Some(4)
val r = when(a){
is Some -> a.t
is None -> 0
}
Not really pattern matching but…
inheritance
15. Types: recursive structure
sealed class LinkedList<out A>
data class Cons<A>(
val head: A,
val tail: LinkedList<A>
): LinkedList<A>()
object Nil: LinkedList<Nothing>()
17. Ad-hoc polymorphism
• Technique popularized by Haskell (typeclasses)
• A set of pure functions to fulfill a contract (laws)
• Abstraction over a general property
• No native support in Kotlin (yet)
18. Order Typeclass
interface Order<T> {
/**
* Compare [x] with [y].
* Returns an Int whose sign is:
* - negative if `x < y`
* - zero if `x = y`
* - positive if `x > y`
*/
fun compare(x: T, y: T): Int
}
19. Order Typeclass instance
object IntOrderInstance: Order<Int> {
override fun compare(x: Int, y: Int): Int
= when {
x < y -> -1
x > y -> 1
else -> 0
}
}
20. Usage of Order
/**
* Sort [list] in croissant order.
* Typeclass instance passing by parameter
*/
fun <T> sort(list: List<T>, O: Order<T>):
List<T> = TODO()
Sorting requires a property of ordering
21. Higher Kinds 101
• Let’s say we have a typeclass SomeTC<F>
• Constraint on F : to be a type shaped like SomeType<A>
• SomeType<A> is a type constructor
with one parameter: A
• Reasoning on theses shapes allows better abstractions
22. Higher Kind emulation
// Kind definition
interface Kind<out F, out A>
typealias Kind2<F,A,B> = Kind<Kind<F,A>,B>
typealias Kind3<F,A,B,C> = Kind<Kind2<F,A,B>,C>
Type aliasing: makes code more readable
Type constructor parameter
Marker type independent of A
23. Higher Kind emulation
• Going back to our LinkedList<A>
• Its shape is Kind<F, A>
class ForLinkedList private constructor()
typealias LListOf<A> = Kind<ForLinkedList, A>
sealed class LinkedList<out A>: LListOf<A> { //…
24. Higher Kinds & Typeclasses
interface Functor<F> {
fun <A, B> map(v: Kind<F, A> ,f: (A) -> B):
Kind<F, B>
}
// Emulation drawback : downcasting…
val l: Kind<ForLinkedList, Int> = //Some call to map()
val fixed: LinkedList<Int> = l.fix()
// Definition of fix()😱 :
fun <A> OptionOf<A>.fix(): Option<A> =
this as Option<A>
Need to downcast !
26. Meet Arrow
• A library based on the principles we just saw
• Inspired by Cats & Scalaz from Scala ecosystem
• Uses code generation (for now) to reduce boilerplate
• Lots of modules & contributors
• Integrates with other libraries from the Kotlin ecosystem
27. Arrow typeclasses
• Provides extensions methods :
interface Functor<F> {
fun <A, B> Kind<F, A>.map(f: (A) -> B):
Kind<F, B>
}
• Still no way to avoid fix() . Go vote for KEEP-87 !
• Provides instances for common datatypes
• Uses KAPT to generate boilerplate
• Enhanced syntax
28. Option
val a: Option<Int> = 3.some()
val b: Option<Int> = 5.some()
val res: Option<Int> = a.flatMap {
x -> b.map { it + x }
}
29. Option 2
val a: Option<Int> = 3.some()
val b: Option<Int> = 5.some()
val res = ForOption extensions {
binding {
a.bind() + b.bind()
}.fix()
}
30. Sample: Validation
// THE CONTEXT
import arrow.core.*
import arrow.data.*
typealias AppRawConfig = MapK<String, String>
fun retrieveConf(): AppRawConfig = TODO()
To use Arrow easily
31. Either & Option
val conf = retrieveConf()
val someParam: Either<String, Int> =
conf.getOption("someParam" ).fold(
{
"Could not find someParam".left()
},{ toParse ->
val nullable: Int? = toParse.toIntOrNull()
nullable.toOption().fold({
"someParam is not an Int".left()
},{
it.right()
})
}
)
Addition from MapK
Extension Method
Kotlin feature
32. Accumulating failures
• We need a similar structure to Either<L,R> :
Validated<E,A>
• How to accumulate values on the E type ?
• NonEmptyList<A> or Nel<A> makes sure a list contains
at least one element
• We will accumulate in Validated<Nel<String>, T>
33. ValidatedNel
val otherParam: Either<String, Int> = //TODO()
val someParamV: Validated<String, Int> =
Validated.fromEither(someParam)
val otherParamV: Validated<String, Int> =
Validated.fromEither(otherParam)
val someParamVNel: ValidatedNel<String, Int>
= someParamV.toValidatedNel()
Lifting Either to Validated
Transforming the left type to Nel
34. Accumulating
data class SomeConfig(val a: Int, val b: Int)
val otherParamVNel: ValidatedNel<String, Int> = // …
val result: ValidatedNel<String, SomeConfig>
= ValidatedNel.applicative(
Nel.semigroup<String>()
)
.map(someParamVNel, otherParamVNel){ t ->
SomeConfig(t.a, t.b)
}.fix()
35. More Arrow
• Optics
• Recursion schemes
• Integration with Kotlin coroutines, RxJava, Reactor,…
• IO
• Uses much more Kotlin idioms (delegation,…)