: 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

\oplus
\oplus
=
==
\oplus
\oplus
=
==

UNIX anyone?

|

UNIX Philosophy

Douglas McIlroy

The Original Creator of Unix Pipeline

  1. Make each program do one thing well.To do a new job, build afresh rather than complicate old programs by adding new "features".

     
  2. 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>
)
A \times B
A×BA \times B

Sum type

A + B
A+BA + B
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!

T_1 + (T_2 \times T_3 \times T_4) + (T_5 \times (T_6 + T_7))
T1+(T2×T3×T4)+(T5×(T6+T7))T_1 + (T_2 \times T_3 \times T_4) + (T_5 \times (T_6 + T_7))

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

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

Summary

The End.

composable kotlin

By Supanat IBoss Potiwarakorn