Kotlin Coroutines
Mario Arias - Cake Solutions Ltd,
Lunch & Learn
March 2018
@dh44t
Slide 2
Slide 2 text
Topics
About me
A tiny introduction to Kotlin
Function types
Extension points
Function composition, currying and partial application
funKTionale’s Option, Either and Disjunction
Future developments
Slide 3
Slide 3 text
Software Engineer at Cake Solutions
12+ years of experience with JVM
technologies
Spring certified trainer
7+ years with Scala
5+ years with Kotlin
funKTionale
Arrow (funTKtionale + Kategory)
RxKotlin original developer and former
team leader
Spring JDBC Kotlin support
Slide 4
Slide 4 text
Co-author of Functional
Kotlin
March 2018
Slide 5
Slide 5 text
What is Kotlin?
Statically typed programming language
for modern multiplatform applications
Slide 6
Slide 6 text
Hello world
package org.cakesolutions.kotlin
fun main(args: Array) {
println("Hello Reddish!")
}
Slide 7
Slide 7 text
Features
•Classes
•sealed (ADT), data
•Interfaces with body methods
•Objects
•Companion objects
•Enum
•Generics
•Named and default parameters
Slide 8
Slide 8 text
Features II
•Control structures
•if else, when
•do, while, for
•Expressions
•Null Safety
•High order functions
•Delegates
•Extension functions
Slide 9
Slide 9 text
Null Safety
package org.cakesolutions.kotlin
val hello0: String = ""
val hello1: String = null //Null can not be a value of a non-null type String
val hello2: String? = null
val reversed0: String? = hello2?.reversed()
val reversed1: String = reversed0.reversed()
Slide 10
Slide 10 text
Null Safety II
Any
String
Nothing
Slide 11
Slide 11 text
Null Safety III
Any
String
Nothing
Any?
String?
Slide 12
Slide 12 text
Functions
/** A function that takes 1 argument. */
public interface Function1 : Function {
/** Invokes the function with the specified argument. */
public operator fun invoke(p1: P1): R
}
Slide 13
Slide 13 text
Functions II
fun doWithTwoNumbers(a: Int, b: Int, f: (Int, Int) Int): Int {
return f(a, b)
}
fun main(args: Array) {
val x = doWithTwoNumbers(2, 3, { a, b a + b })
doWithTwoNumbers(x, 3) { a, b a * b }
}
Slide 14
Slide 14 text
Functions III
Create your own control structures!!
fun unless(conditional: Boolean, body: () Unit) {
if (!conditional) {
body()
}
}
fun main(args: Array) {
val age: Int = //Magic here!
unless(age > 18) {
//Play video games all day long
}
}
Slide 15
Slide 15 text
Delegated property
lazy is NOT a language feature but a library function
val lazyValue: String by lazy {
println("computed")
"Hello"
}
fun main(args: Array) {
println(lazyValue)
println(lazyValue)
}
Slide 16
Slide 16 text
Delegated property
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("") { property, oldValue, newValue
println("$oldValue $newValue")
}
}
fun main(args: Array) {
val user = User()
user.name = "first"
user.name = "second"
}
Slide 17
Slide 17 text
Extension functions
fun String.fromReddish() = "$this, FROM Reddish"
fun main(args: Array) {
println("Hello World".fromReddish())
}
Slide 18
Slide 18 text
Extension functions II
operator fun String.invoke(suffix: String) = "$this$suffix"
fun main(args: Array) {
println("This isn't a function"(""))
}
Slide 19
Slide 19 text
Extension functions III
fun main(args: Array) {
val myList = listOf(1, 2, 3, 4) //call to method reference
.map(InttoString)
.map { i i + i }
.map(StringtoInt)
.filter { it > 20 } // `it` is an implicit parameter
println(myList)
}
Slide 20
Slide 20 text
Combine all the things!!!
one-line unless from Ruby!!
infix fun (() Unit).unless(condition: Boolean) {
if (!condition) {
this()
}
}
fun main(args: Array) {
val age: Int = //Magic
{ /*pay taxes*/ } unless (age < 18)
}
Slide 21
Slide 21 text
Inline
fun time(body: () T): Pair {
val startTime = System.currentTimeMillis()
val v = body()
val endTime = System.currentTimeMillis()
return v to endTime - startTime
}
inline fun inTime(body: () T): Pair {
val startTime = System.currentTimeMillis()
val v = body()
val endTime = System.currentTimeMillis()
return v to endTime - startTime
}
Slide 22
Slide 22 text
Inline II
fun main(args: Array) {
val b = time {
Thread.sleep(1000)
"boring"
}
val i = inTime {
Thread.sleep(1000)
"inline"
}
println(b)
println(i)
}
Slide 23
Slide 23 text
Inline III
@NotNull
public static final Pair time(@NotNull final Function0 body) {
Intrinsics.checkParameterIsNotNull((Object)body, "body");
final long startTime = System.currentTimeMillis();
final Object v = body.invoke();
final long endTime = System.currentTimeMillis();
return (Pair)TuplesKt.to(v, (Object)(endTime - startTime));
}
@NotNull
public static final Pair inTime(@NotNull final Function0 body) {
Intrinsics.checkParameterIsNotNull((Object)body, "body");
final long startTime = System.currentTimeMillis();
final Object v = body.invoke();
final long endTime = System.currentTimeMillis();
return (Pair)TuplesKt.to(v, (Object)(endTime - startTime));
}
Decompiled bytecode
Slide 24
Slide 24 text
Inline IV
public static final void main(@NotNull final String[] args) {
Intrinsics.checkParameterIsNotNull((Object)args, "args");
final Pair b = time(
(kotlin.jvm.functions.Function0)_05_inlineKt$main$b._05_inlineKt$main$b$1.INSTANCE
);
final long startTime$iv = System.currentTimeMillis();
Thread.sleep(1000L);
final Object v$iv = "inline";
final long endTime$iv = System.currentTimeMillis();
final Pair i = TuplesKt.to(v$iv, (Object)(endTime$iv - startTime$iv));
System.out.println(b);
System.out.println(i);
}
Slide 25
Slide 25 text
Inline V
static final class _05_inlineKt$main$b$1 extends Lambda implements Function0 {
public static final _05_inlineKt$main$b$1 INSTANCE;
@NotNull
public final String invoke() {
Thread.sleep(1000L);
return "boring";
}
static {
_05_inlineKt$main$b$1.INSTANCE = new _05_inlineKt$main$b$1();
}
}
Slide 26
Slide 26 text
Type-safe builders
fun logged(body: Logger.() T): T {
val log = LoggerFactory.getLogger("builder")
return log.body()
}
fun main(args: Array) {
val i = logged {
info("THIS is INFO")
debug("THIS is DEBUG")
error("THIS is ERROR")
1 + 2
}
}
Slide 27
Slide 27 text
DSLs
class FxApp : App(FxViewclass)
class FxView : View() {
override val root = vbox {
label("Reddish App")
button("Press me")
}
}
fun main(args: Array) {
Application.launch(FxAppclass.java, *args)
}
Slide 28
Slide 28 text
Coroutines
but first, Threads
import kotlin.concurrent.thread
fun main(args: Array) {
val computation = thread {
Thread.sleep(1000)
println("World!")
}
print("Hello, ")
computation.join()
}
Slide 29
Slide 29 text
Hardware and JVM Threads
a simplistic, and incomplete, explanation
Core 1
T1 T2 T3
Core 2
T4 T.. TN
Slide 30
Slide 30 text
Monitoring utils
VisualVM
class MonitorUtil {
private val scanner = Scanner(System.`in`)
fun awaitEnter(){
println("Press enter to continue")
scanner.nextLine()
}
}
fun monitored(body: MonitorUtil.() Unit) {
val monitor = MonitorUtil()
monitor.body()
monitor.awaitEnter()
}
Executors
fun main(args: Array) = monitored {
awaitEnter()
val pool = Executors.newFixedThreadPool(128)
repeat(10000) {
pool.submit {
Thread.sleep(1000)
print('.')
}
}
pool.shutdown()
pool.awaitTermination(2, TimeUnit.SECONDS)
}
Slide 33
Slide 33 text
Coroutines
“..a coroutine is a very light thread that runs a block of
code and has a similar life cycle, but can complete with
a return value or an exception. Technically, a coroutine
is an instance of a suspendable computation, a
computation that may suspend. Coroutines aren't
bound to a particular thread and can suspend in one
Thread and resume execution in a different one”
From the book Functional Kotlin
Slide 34
Slide 34 text
Coroutines II
Core 1
T1 T2 T3
Core 2
T4 T.. TN
Coroutines
Slide 35
Slide 35 text
Coroutines III
import kotlinx.coroutines.experimental.delay
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.runBlocking
fun main(args: Array) = runBlocking {
val job = launch {
delay(1000)
println("World!")
}
print("Hello, ")
job.join()
}
Slide 36
Slide 36 text
Coroutines IV
Concept Description
Coroutine Light thread
Suspending function A function with a suspend modifier. It can
suspend a coroutine without blocking the thread
Suspending lambda A lambda function with a suspend modifier
Coroutine builder A function that takes a suspending lambda
Suspension point A point where a suspending function is invoked
Continuation The state of a suspended coroutine at a
suspension point
Slide 37
Slide 37 text
Coroutines V
fun main(args: Array) = runBlocking {
val job = launch {
delay(1000)
println("World!")
}
print("Hello, ")
job.join()
}
Suspension point
Suspension point
Suspending function
Coroutine builder
Coroutine builder
Continuation
Suspending lambda
Suspending lambda
Suspending function
Slide 38
Slide 38 text
10000 Coroutines
fun main(args: Array) = monitored {
awaitEnter()
runBlocking {
val jobs = List(10000) {
launch {
delay(1000)
print(".")
}
}
jobs.forEach { it.join() }
}
}
Slide 39
Slide 39 text
Shared state
suspend fun repeatInParallel(times: Int, block: suspend () Unit) {
val job = launch {
repeat(times) {
launch(coroutineContext) {
block()
}
}
}
job.join()
}
Slide 40
Slide 40 text
Shared state II
fun main(args: Array) = runBlocking {
var counter = 0
val time = measureTimeMillis {
repeatInParallel(1_000_000) {
counter
}
}
println("counter = $counter") //EPIC FAIL
println("time = $time")
}
Slide 41
Slide 41 text
Atomic Integer
import java.util.concurrent.atomic.AtomicInteger
fun main(args: Array) = runBlocking {
val counter = AtomicInteger(0)
val time = measureTimeMillis {
repeatInParallel(1_000_000) {
counter.incrementAndGet()
}
}
println("counter = ${counter.get()}")
println("time = $time")
}
Slide 42
Slide 42 text
Single thread context
fun main(args: Array) = runBlocking {
var counter = 0
val counterContext = newSingleThreadContext("CounterContext")
val time = measureTimeMillis {
repeatInParallel(1_000_000) {
withContext(counterContext) {
counter
}
}
}
println("counter = $counter")
println("time = $time")
}
Slide 43
Slide 43 text
Mutex
fun main(args: Array) = runBlocking {
var counter = 0
val mutex = Mutex()
val time = measureTimeMillis {
repeatInParallel(1_000_000) {
mutex.withLock {
counter
}
}
}
println("counter = $counter")
println("time = $time")
}
Slide 44
Slide 44 text
Actors I
sealed class CounterMsg
object IncCounter : CounterMsg()
class GetCounter(val response: CompletableDeferred) : CounterMsg()
fun counterActor(start: Int) = actor {
var counter = start
for (msg in channel) {
when (msg) {
is IncCounter counter
is GetCounter msg.response.complete(counter)
}
}
}
Slide 45
Slide 45 text
Actors II
fun main(args: Array) = runBlocking {
val counterActor = counterActor(0)
val time = measureTimeMillis {
repeatInParallel(1_000_000) {
counterActor.send(IncCounter)
}
}
val counter = CompletableDeferred()
counterActor.send(GetCounter(counter))
println("counter = ${counter.await()}")
println("time = $time")
}
Slide 46
Slide 46 text
Other uses
import arrow.core.Some
fun main(args: Array) {
val a = Some(1)
val b = Some(2)
val c = a.flatMap { x
b.map { y x + y }
}
println("c = $c")
}
Slide 47
Slide 47 text
Monadic comprehension
import arrow.core.Option
import arrow.core.Some
import arrow.core.ev
import arrow.core.monad
import arrow.typeclasses.binding
fun main(args: Array) {
val a = Some(1)
val b = Some(2)
val c = Option.monad().binding {
val x = a.bind()
val y = b.bind()
x + y
}.ev()
println("c = $c")
}
continuation: flatMap
continuation: map