Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Functional Programming in Kotlin with funKTionale

Mario Arias
December 10, 2016

Functional Programming in Kotlin with funKTionale

Talk for the Belarus Functional Programming Conference f(by) 2016 #funcBy

Mario Arias

December 10, 2016
Tweet

More Decks by Mario Arias

Other Decks in Technology

Transcript

  1. Functional programming in
    Kotlin with funKTionale
    Mario Arias - f(by) 2016
    @dh44t

    View Slide

  2. 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

    View Slide

  3. Software Engineer at Cake Solutions
    10+ years of experience with JVM
    technologies
    Spring certified trainer
    5+ years with Scala
    3+ years with Kotlin
    funKTionale
    KotlinPrimavera
    RxKotlin original developer and former
    team leader
    NOT an expert on functional programming

    View Slide

  4. What is Kotlin?
    Statically typed programming language
    for the JVM, Android and the browser*
    *LLVM and iOS coming in the future

    View Slide

  5. Hello world
    fun main(args: Array) {

    println("Hello Minsk!")

    }

    View Slide

  6. Features
    •Classes
    •sealed (ADT), data
    •Interfaces with body methods
    •Objects
    •Companion objects
    •Enum
    •Generics
    •Named and default parameters

    View Slide

  7. Features II
    •Control structures
    •if else, when
    •do, while, for
    •Expressions
    •Null Safety
    •High order functions
    •Delegates
    •Extension functions

    View Slide

  8. Null Safety
    val hello: String = “"

    val hello: String = null //compiler error

    val hello: String? = null

    val reversed: String? = hello?.reversed()

    val otherReversed: String = reversed !!.reversed() //NPE

    View Slide

  9. Null Safety II
    Any
    String
    Nothing

    View Slide

  10. Null Safety III
    Any
    String
    Nothing
    Any?
    String?

    View Slide

  11. 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

    }

    View Slide

  12. Functions II
    fun doWithTwoNumbers(a: Int, b: Int, f: (Int, Int) -> Int): Int {

    return f(a, b)

    }


    val x = doWithTwoNumbers(2, 3, { a, b -> a + b })

    doWithTwoNumbers(x, 3) { a, b -> a * b }

    View Slide

  13. Functions III
    fun unless(conditional: Boolean, body: () -> Unit) {

    if (!conditional) {

    body()

    }

    }


    unless(age > 18) {

    //Do something awesome

    }
    Create your own control structures!!

    View Slide

  14. Delegates
    package org.funktionale.partials


    class PartialFunction(val definetAt: (P1) -> Boolean, f: (P1) -> R)
    : Function1 by f {

    fun isDefinedAt(p1: P1) = this.definetAt(p1)

    }

    View Slide

  15. Delegated property
    val lazyValue: String by lazy {

    println("computed!")

    "Hello"

    }


    fun main(args: Array) {

    println(lazyValue)

    println(lazyValue)


    }
    lazy is NOT a language feature but a library function

    View Slide

  16. Delegated property
    class User {

    var name: String by Delegates.observable("") {

    prop, old, new ->

    println("$old -> $new")

    }

    }


    fun main(args: Array) {

    val user = User()

    user.name = "first"

    user.name = "second"

    }

    View Slide

  17. Extension functions
    fun String.fromMinsk() = "$this, FROM Minsk"


    println("Hello world".fromMinsk())

    View Slide

  18. Extension functions II
    operator fun String.invoke(suffix:String) = "$this$suffix"


    println("This isn't a function"(" !!"))

    View Slide

  19. Extension functions III
    val myList = listOf(1, 2, 3, 4)

    .map(Int ::toString) //call to method reference

    .map { i -> i + i }

    .map(String ::toInt)

    .filter { it > 20 } // it is an implicit parameter


    println(myList)

    View Slide

  20. Combine all the things!!!
    infix fun (() -> Unit).unless(condition: Boolean) {

    if (!condition) {

    this()

    }

    }



    { /*do some magic */ } unless (age < 18)
    one-line unless from Ruby!!

    View Slide

  21. funKTionale
    Functional constructs and patterns for Kotlin
    Currently at 0.9.7 version

    View Slide

  22. Function composition
    A technique to create a new function using two
    existing functions.
    % ps aux | grep java

    View Slide

  23. infix fun Function1.andThen(f: (IP)  R): (P1)  R = forwardCompose(f)


    infix fun Function1.forwardCompose(f: (IP)  R): (P1)  R {

    return { p1: P1  f(this(p1)) }

    }


    infix fun Function1.compose(f: (P1)  IP): (P1)  R {

    return { p1: P1  this(f(p1)) }

    }


    View Slide

  24. fun main(args: Array) {

    val conf = SparkConf().setMaster("local").setAppName("My App")

    val sc = JavaSparkContext(conf)

    val split: (String) -> List = { it.split("|") }

    val upper: (String) -> String = { it.toUpperCase() }

    val user: (List) -> User = { User(it[0], it[1].toInt()) }

    val users = sc.textFile("s3://path/to/my-petabyte-file.txt")

    .map(upper)

    .map(split)

    .map(user)


    users.take(20).forEach { println(it) }

    }
    import org.funktionale.composition.andThen
    fun main(args: Array) {

    val conf = SparkConf().setMaster("local").setAppName("My App")

    val sc = JavaSparkContext(conf)

    val split: (String) -> List = { it.split("|") }

    val upper: (String) -> String = { it.toUpperCase() }

    val user: (List) -> User = { User(it[0], it[1].toInt()) }

    val users = sc.textFile("s3://path/to/my-petabyte-file.txt")

    .map(upper andThen split andThen user)


    users.take(20).forEach { println(it) }

    }
    Each map() transformation could be
    potentially distributed across nodes/
    partitions*
    Just one map() transformation
    composed by several functions
    * Yes, Apache Spark is compatible with Kotlin

    View Slide

  25. Currying

    View Slide

  26. http://singletrackworld.com/forum/topic/nice-simple-tasty-curry-recipe

    View Slide

  27. Currying
    Transforming a function of arity n into
    a sequence of n functions with arity 1
    (x, y, z) => r
    (x) => (y) => (z) => r
    https://wiki.haskell.org/Haskell_Brooks_Curry

    View Slide

  28. fun Function2.curried(): (P1)  (P2)  R {

    return { p1: P1  { p2: P2  this(p1, p2) } }

    }



    fun Function3.curried(): (P1)  (P2)  (P3)  R {

    return { p1: P1  { p2: P2  { p3: P3  this(p1, p2, p3) } } }

    }

    //… all the way to Function22

    View Slide

  29. fun main(args: Array) {

    val conf = SparkConf().setMaster("local[*]").setAppName("ML")

    val sc = JavaSparkContext(conf)


    val spam = sc.textFile("spam.txt")

    val ham = sc.textFile("ham.txt")


    val tf = HashingTF(10000)


    val posExamples = ham.map { LabeledPoint(1.0, tf.transform(listOf(it.split(" ")))) }

    val negExamples = spam.map { LabeledPoint(0.0, tf.transform(listOf(it.split(" ")))) }

    val trainData = posExamples.union(negExamples)

    trainData.cache()

    val model = LogisticRegressionWithLBFGS().run(trainData.rdd())

    }
    import org.funktionale.currying.curried
    fun main(args: Array) {

    val conf = SparkConf().setMaster("local[*]").setAppName("ML")

    val sc = JavaSparkContext(conf)


    val spam = sc.textFile("spam.txt")

    val ham = sc.textFile("ham.txt")


    val tf = HashingTF(10000)


    val labeling = { label: Double, email: String ->

    LabeledPoint(label, tf.transform(listOf(email.split(" "))))

    }

    val curried = labeling.curried()

    val posExamples = ham.map(curried(1.0))

    val negExamples = spam.map(curried(0.0))

    val trainData = posExamples.union(negExamples)

    trainData.cache()

    val model = LogisticRegressionWithLBFGS().run(trainData.rdd())

    }

    View Slide

  30. Partial applied functions
    Calling a function with less parameters than the
    function’s arity (fixing parameters) will return a new
    function with a smaller arity
    f(x,y,z)
    f(1,2) => g(z)

    View Slide

  31. fun Function2.partially1(p1: P1): (P2)  R {

    return { p2: P2  this(p1, p2) }

    }



    fun Function2.partially2(p2: P2): (P1)  R {

    return { p1: P1  this(p1, p2) }

    }



    fun Function3.partially1(p1: P1): (P2, P3)  R {

    return { p2: P2, p3: P3  this(p1, p2, p3) }

    }



    fun Function3.partially2(p2: P2): (P1, P3)  R {

    return { p1: P1, p3: P3  this(p1, p2, p3) }

    }



    fun Function3.partially3(p3: P3): (P1, P2)  R {

    return { p1: P1, p2: P2  this(p1, p2, p3) }

    }
    //All the way to Function22

    View Slide

  32. fun Function2.invoke(p1: P1, partial2: Partial = partial()): (P2)  R {

    return { p2: P2  this(p1, p2) }

    }



    fun Function2.invoke(partial1: Partial = partial(), p2: P2): (P1)  R {

    return { p1: P1  this(p1, p2) }

    }



    fun Function3.invoke(p1: P1, partial2: Partial = partial(), partial3: Partial = partial()): (P2, P3)  R {

    return { p2: P2, p3: P3  this(p1, p2, p3) }

    }



    fun Function3.invoke(partial1: Partial = partial(), p2: P2, partial3: Partial = partial()): (P1, P3)  R {

    return { p1: P1, p3: P3  this(p1, p2, p3) }

    }



    fun Function3.invoke(partial1: Partial = partial(), partial2: Partial = partial(), p3: P3): (P1, P2)  R {

    return { p1: P1, p2: P2  this(p1, p2, p3) }
    }
    //All the way to Function22…. This file has 2532 lines


    View Slide

  33. dr_id dr_name dr_age
    1 Carlos 37
    2 Laura 28
    3 Ari 25
    doctors
    p_id p_name p_age
    1 Andy 55
    2 Riba 26
    3 Joanie 33
    5 Ben 32
    6 Pete 45
    patients
    n_id n_name n_age
    1 Diana 33
    2 Sarah 28
    3 Liz 26
    nurses

    View Slide

  34. import org.kotlinprimavera.jdbc.core.extract
    val doctors = template.query("select * from doctors") { rs, i ->

    rs.extract { //DSL from KotlinPrimavera

    User(string["dr_name"]!!, int["dr_age"]!!)

    }

    }


    val nurses = template.query("select * from nurses") { rs, i ->

    rs.extract {

    User(string["n_name"]!!, int["n_age"]!!)

    }

    }


    val patients = template.query("select * from patients") { rs, i ->

    rs.extract {

    User(string["p_name"]!!, int["p_age"]!!)

    }

    }
    import org.kotlinprimavera.jdbc.core.extract
    import org.funktionale.partials.*
    val mapper: (ResultSet, Int, String) -> User = { rs, i, prefix ->

    rs.extract { //DSL from KotlinPrimavera

    User(string["${prefix}_name"]!!, int["${prefix}_age"]!!)

    }

    }


    val doctors = template.query("select * from doctors", mapper(p3 = "dr"))


    val nurses = template.query("select * from nurses", mapper.partially3("n"))


    val patients = template.query("select * from patients", mapper(p3 = "p"))

    View Slide

  35. Option
    Why do you want Option in a null-safe language ??

    View Slide

  36. Option is a type that represent the
    existence or absence of a meaningful value
    Examples of meaningful value
    • Succesful operation (no exceptions)
    • An existent value (record in DB)
    • An useful value (non-empty list)

    View Slide

  37. Examples without Option
    Representation Example Problems
    A value of the same type
    defined by convention
    indexOf(x) will return -1 if x
    doesn’t exists in the
    structure (Array, List)
    • Is not mandatory to
    check
    • Based on oral tradition
    An exception
    Spring’s JdbcTemplate will
    throw an EmptyResultDAE if
    no record is available*
    • Runtime Exception
    • Exception-based logic
    null
    Hibernate will return null if
    no record is available
    • is null
    * I kinda like it

    View Slide

  38. Option in a null-safe
    language
    //scala 2.11
    val op1:Option[String] = null // :crying_cat_face:

    val op2:Option[String] = Some(null) // :crying_cat_face:
    //kotlin

    val op1: Option = null //compilation error

    val op2: Option = Some(null) // compilation error


    val op3: Option? = null // really ??

    val op4: Option = Some(null) // do you really want this?

    op4.map { s -> s !!.reversed() }
    val op5: Option = Some("funcBy")

    View Slide

  39. fun divide(num: Int, den: Int): Int? {

    return if (num % den != 0) {

    null

    } else {

    num / den

    }

    }


    fun division(a: Int, b: Int, c: Int): Pair? {

    val ac = divide(a, c)

    return when (ac) {

    is Int -> {

    val bc = divide(b, c)

    when (bc) {

    is Int -> ac to bc

    else -> null

    }

    }

    else -> null

    }

    }
    An example*
    Based on Ken Barclay’s* post http://kenbarclay.blogspot.co.uk/2014/02/kotlin-option-type-2.html
    division function let me check if two numbers (a,b) are divisible by a third one (c)

    View Slide

  40. fun divide(num: Int, den: Int): Option {

    return if (num % den != 0) {

    None

    } else {

    Some(num / den)

    }

    }


    fun division(a: Int, b: Int, c: Int): Option> {

    val ac = divide(a, c)

    return when (ac) {

    is Some -> {

    val bc = divide(b, c)

    when (bc) {

    is Some -> {

    Some(ac.get() to bc.get())

    }

    else -> None

    }

    }

    else -> None

    }

    }
    An example*
    Based on Ken Barclay’s* post http://kenbarclay.blogspot.co.uk/2014/02/kotlin-option-type-2.html
    Migrated to Option

    View Slide

  41. fun division(a: Int, b: Int, c: Int): Option> {

    return divide(a, c).flatMap { ac ->

    divide(b, c).flatMap { bc ->

    Some(ac to bc)

    }

    }

    }
    division with flatMap
    fun division(a: Int, b: Int, c: Int): Option> {

    val ac = divide(a, c)

    return when (ac) {

    is Some -> {

    val bc = divide(b, c)

    when (bc) {

    is Some -> {

    Some(ac.get() to bc.get())

    }

    else -> None

    }

    }

    else -> None

    }

    }
    = replaced by 1st flatMap
    = replaced by 2nd flatMap

    View Slide

  42. Either
    An unbiased representation of two possibles return
    values (left or right), commonly used to represent an
    error or a successful operation

    View Slide

  43. Either II
    data class User(val name: String)


    fun getUser(url: String): Either {
    //do some magic

    }


    val either = getUser("http: //myapi.com/user/1")

    when(either){

    is Left -> println(either.left().get())

    is Right -> println(either

    .right().map { it.copy(name = it.name.capitalize()) }

    .right().get())

    }

    View Slide

  44. Either III
    data class User(val name: String)


    fun getUser(url: String): Either = eitherTry {

    // do some magic

    }

    val either = getUser("http: //myapi.com/user/1")

    when(either){

    is Left -> println(either.left().get().message)

    is Right -> println(either

    .right().map { it.copy(name = it.name.capitalize()) }

    .right().get())

    }

    View Slide

  45. Disjunction
    A right-biased Either

    View Slide

  46. Disjunction II
    fun getUser(url: String): Disjunction = disjunctionTry {

    //do some magic

    }


    val disjunction = getUser("http: //myapi.com/user/1")

    when(disjunction){

    is Left -> println(disjunction.swap().get().message)

    is Right -> println(disjunction

    .map { it.copy(name = it.name.capitalize()) }

    .get())

    }

    View Slide

  47. Disjunction III
    val user1 = getUser("http: //myapi.com/user/1")

    val user2 = getUser("http: //myapi.com/user/2")


    val userList: Disjunction> = user1.flatMap { u1 ->

    user2.map { u2 ->

    listOf(u1, u2)

    }

    }


    val userList2: Disjunction, List> = validate(user1,user2){ u1, u2 ->

    listOf(u1, u2)

    }

    View Slide

  48. Future developments
    •Kotlin 1.1
    •Type Alias
    •Coroutines
    •funKTionale 1.0
    •Clean up
    •Modularisation*
    •funKTionale 1.1 +
    •More monads (State and others)
    * funKTionale-0.9.7.jar (1.3 MB) > kotlin-stdlib-1.0.5-2.jar (564k)

    View Slide

  49. Thanks!!
    (PS: We’re hiring, contact me :) )

    View Slide