: KomposableKotlin



Supanat Potiwarakorn (aka. I'Boss)
Software Choreographer / Consultant @ Thoughtworks

- I used Kotlin on server-side
- craze about functional programming
- Dis Javascript all the time
- But can never escape from it
- This is a waste of time, just get to the point oredi!
Yea, composable Kotlin. But mate, what does "compose" even mean?

it's designed to be connected
composed component can still be to composed further
(no adapter needed)
The pieces are reusable in many context
the act of putting together pieces is associative
UNIX anyone?
|

UNIX Philosophy
Douglas McIlroy
The Original Creator of Unix Pipeline
-
Make each program do one thing well.To do a new job, build afresh rather than complicate old programs by adding new "features".
- Expect the output of every program to become the input to another, as yet unknown, program. Don't clutter output with extraneous information.
( ... 2 more )
> ls -a | egrep "\.md$" | wc -l
the programs are designed to be connected
piped program can still be piped furthur
they are reusable in many context
the act of putting together pieces is associative
> lsmd () { ls -a | egrep "\.md$" }
> countmd() { egrep "\.md$" | wc -l }
> # =================================
> lsmd | wc -l
> # is equal to
> ls -a | countmd
Composable
- designed to be connected
- reusable
- composed component can be composed further
- associative
How can we write composable Kotlin code?
data
operation
Object
I just want the data!
I just want the operation!
data
operation
data class
data structure
function
Data!
known fact:
Kotlin is a statically typed language
ADT
Algebraic Data Type
Sum type
Product Type
Product type
Pair(1, "K") // Building block!
Pair(1, Pair("K", 3))
// is equal up to isomorphism to
Pair(Pair(1, "K"), 3)
// is equal up to isomorphism to
Triple(1, "K", 3)
// Product type compose! And we can build product type with any length
data class Event(
name: String,
owner: User,
mods: List<User>,
tickets: List<Ticket>
)
Sum type
sealed class UserEvent {
object Shake: UserEvent()
data class Touch(val x: Double, val y: Double): UserEvent()
data class Type(val char: Char): UserEvent()
}
sealed class Tree<T> {
data class Leaf<T>(val value: T) : Tree<T>()
data class Node<T>(val left: Tree<T>, val right: Tree<T>) : Tree<T>()
}
Now we have data with composable data types!
separate data from operation
=
separation of concern
now we can start designing by starting with
"Ignoring about how are they going to behave.
How should things that is going to live in our system look like?"
Going further
"Making illegal state irrepresentable"
Function!
Pure!
sealed class Switch(open val resistance: Double) {
data class On(val current: Double, override val resistance: Double): Switch(resistance)
data class Off(override val resistance: Double): Switch(resistance)
}
fun measureCurrent(switch: Switch) = when (switch) {
is Switch.On -> switch.current
is Switch.Off -> 0.0
}
var allEven = true
fun isAllEven(list: List<Int>): Unit {
list.map {
if (it % 2 != 0) {
allEven = false
}
}
}
fun sumUntilEq(target: Int, numbers: List<Int>): Int {
var sum = 0
numbers.forEach {
if (it == target) return sum else sum += it
}
return sum
}
It's time to compose!
// compose with `*`
// (f * g)(x) = f(g(x))
operator fun <A, B, C> ((B) -> C).times(f: (A) -> B): (A) -> C = { this(f(it)) }
val add1 = { n: Int -> n + 1 }
val times2 = { n: Int -> n * 2 }
val f = times2 * add1
f(8) // 18
fun measureVoltage(switch: Switch) =
measureCurrent(switch) * switch.resistance
fun addResistance(newResistance: Double) = { switch: Switch ->
when (switch) {
is Switch.On -> switch.copy(resistance = switch.resistance + newResistance)
is Switch.Off -> switch.copy(resistance = switch.resistance + newResistance)
}
}
measureVoltageOfAdded10Ohm(Switch.On(10.0, 25.0))
// V = I * R = 10 * (25 + 5 + 5) = 350
measureVoltageOfAdded10Ohm(Switch.Off(10.0))
// 0.0
val measureVoltageOfAdded10Ohm: (Switch) -> Double =
::measureVoltage * addResistance(5.0) * addResistance(5.0)

`let`'s fix it!
and now we have something like UNIX pipe!
Switch.On(10.0, 25.0)
.let(addResistance(5.0))
.let(addResistance(5.0))
.let(::measureVoltage)
// 350.0


Living Together
Functional core, imperative shell

Composable Effects!

Arrow-kt
fun switchOn(switch: Switch, inCurrent: Double) = when (switch) {
is Switch.On -> throw BlowUpException()
is Switch.Off -> Switch.On(inCurrent, switch.resistance)
}
fun trySwitchOn(switch: Switch, inCurrent: Double) = Try {
when (switch) {
is Switch.On -> throw BlowUpException()
is Switch.Off -> Switch.On(inCurrent, switch.resistance)
}
}
Switch.On(10.0, 25.0)
.let(addResistance(5.0))
.let(addResistance(5.0))
.let(::measureVoltage)
// 350.0
trySwitchOn(Switch.Off(25.0), 10.0)
.map(addResistance(5.0))
.map(addResistance(5.0))
.map(::measureVoltage)
.fold(
ifFailure = { 0.0 },
ifSuccess = { it }
)
// 350.0
trySwitchOn(Switch.On(10.0, 25.0), 10.0)
.map(addResistance(5.0))
.map(addResistance(5.0))
.map(::measureVoltage)
.fold(
ifFailure = { 0.0 },
ifSuccess = { it }
)
// 0.0
- composable means
- reusable
- can compose the composed
- asscociative
- seperate data and operation apart to be more composable
- side-effect can be handled by
- push them to the shell:
functional core imperative shell - composable effect:
monads, applicatives
- push them to the shell:
Summary
The End.
composable kotlin
By Supanat IBoss Potiwarakorn
composable kotlin
- 889