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. 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
  2. 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
  3. What is Kotlin? Statically typed programming language for the JVM,

    Android and the browser* *LLVM and iOS coming in the future
  4. Features •Classes •sealed (ADT), data •Interfaces with body methods •Objects

    •Companion objects •Enum •Generics •Named and default parameters
  5. Features II •Control structures •if else, when •do, while, for

    •Expressions •Null Safety •High order functions •Delegates •Extension functions
  6. 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
  7. Functions /** A function that takes 1 argument. */
 public

    interface Function1<in P1, out R> : Function<R> {
 /** Invokes the function with the specified argument. */
 public operator fun invoke(p1: P1): R
 }
  8. 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 }
  9. Functions III fun unless(conditional: Boolean, body: () -> Unit) {


    if (!conditional) {
 body()
 }
 }
 
 unless(age > 18) {
 //Do something awesome
 } Create your own control structures!!
  10. Delegates package org.funktionale.partials
 
 class PartialFunction<in P1, out R>(val definetAt:

    (P1) -> Boolean, f: (P1) -> R) : Function1<P1, R> by f {
 fun isDefinedAt(p1: P1) = this.definetAt(p1)
 }
  11. Delegated property val lazyValue: String by lazy {
 println("computed!")
 "Hello"


    }
 
 fun main(args: Array<String>) {
 println(lazyValue)
 println(lazyValue)
 
 } lazy is NOT a language feature but a library function
  12. Delegated property class User {
 var name: String by Delegates.observable("<no

    name>") {
 prop, old, new ->
 println("$old -> $new")
 }
 }
 
 fun main(args: Array<String>) {
 val user = User()
 user.name = "first"
 user.name = "second"
 }
  13. 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)
  14. Combine all the things!!! infix fun (() -> Unit).unless(condition: Boolean)

    {
 if (!condition) {
 this()
 }
 }
 
 
 { /*do some magic */ } unless (age < 18) one-line unless from Ruby!!
  15. Function composition A technique to create a new function using

    two existing functions. % ps aux | grep java
  16. infix fun<P1, IP, R> Function1<P1, IP>.andThen(f: (IP)  R): (P1)

     R = forwardCompose(f)
 
 infix fun<P1, IP, R> Function1<P1, IP>.forwardCompose(f: (IP)  R): (P1)  R {
 return { p1: P1  f(this(p1)) }
 }
 
 infix fun<IP, R, P1> Function1<IP, R>.compose(f: (P1)  IP): (P1)  R {
 return { p1: P1  this(f(p1)) }
 }

  17. fun main(args: Array<String>) {
 val conf = SparkConf().setMaster("local").setAppName("My App")
 val

    sc = JavaSparkContext(conf)
 val split: (String) -> List<String> = { it.split("|") }
 val upper: (String) -> String = { it.toUpperCase() }
 val user: (List<String>) -> 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<String>) {
 val conf = SparkConf().setMaster("local").setAppName("My App")
 val sc = JavaSparkContext(conf)
 val split: (String) -> List<String> = { it.split("|") }
 val upper: (String) -> String = { it.toUpperCase() }
 val user: (List<String>) -> 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
  18. 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
  19. fun<P1, P2, R> Function2<P1, P2, R>.curried(): (P1)  (P2) 

    R {
 return { p1: P1  { p2: P2  this(p1, p2) } }
 }
 
 
 fun<P1, P2, P3, R> Function3<P1, P2, P3, R>.curried(): (P1)  (P2)  (P3)  R {
 return { p1: P1  { p2: P2  { p3: P3  this(p1, p2, p3) } } }
 }
 //… all the way to Function22 

  20. fun main(args: Array<String>) {
 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<String>) {
 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())
 }
  21. 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)
  22. fun <P1, P2, R> Function2<P1, P2, R>.partially1(p1: P1): (P2) 

    R {
 return { p2: P2  this(p1, p2) }
 }
 
 
 fun <P1, P2, R> Function2<P1, P2, R>.partially2(p2: P2): (P1)  R {
 return { p1: P1  this(p1, p2) }
 }
 
 
 fun <P1, P2, P3, R> Function3<P1, P2, P3, R>.partially1(p1: P1): (P2, P3)  R {
 return { p2: P2, p3: P3  this(p1, p2, p3) }
 }
 
 
 fun <P1, P2, P3, R> Function3<P1, P2, P3, R>.partially2(p2: P2): (P1, P3)  R {
 return { p1: P1, p3: P3  this(p1, p2, p3) }
 }
 
 
 fun <P1, P2, P3, R> Function3<P1, P2, P3, R>.partially3(p3: P3): (P1, P2)  R {
 return { p1: P1, p2: P2  this(p1, p2, p3) }
 } //All the way to Function22
  23. fun <P1, P2, R> Function2<P1, P2, R>.invoke(p1: P1, partial2: Partial<P2>

    = partial()): (P2)  R {
 return { p2: P2  this(p1, p2) }
 }
 
 
 fun <P1, P2, R> Function2<P1, P2, R>.invoke(partial1: Partial<P1> = partial(), p2: P2): (P1)  R {
 return { p1: P1  this(p1, p2) }
 }
 
 
 fun <P1, P2, P3, R> Function3<P1, P2, P3, R>.invoke(p1: P1, partial2: Partial<P2> = partial(), partial3: Partial<P3> = partial()): (P2, P3)  R {
 return { p2: P2, p3: P3  this(p1, p2, p3) }
 }
 
 
 fun <P1, P2, P3, R> Function3<P1, P2, P3, R>.invoke(partial1: Partial<P1> = partial(), p2: P2, partial3: Partial<P3> = partial()): (P1, P3)  R {
 return { p1: P1, p3: P3  this(p1, p2, p3) }
 }
 
 
 fun <P1, P2, P3, R> Function3<P1, P2, P3, R>.invoke(partial1: Partial<P1> = partial(), partial2: Partial<P2> = 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 
 

  24. 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
  25. 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"))
  26. 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)
  27. 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
  28. 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<String> = null //compilation error
 val op2: Option<String> = Some(null) // compilation error
 
 val op3: Option<String>? = null // really ??
 val op4: Option<String?> = Some(null) // do you really want this?
 op4.map { s -> s !!.reversed() } val op5: Option<String> = Some("funcBy")
  29. 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<Int, Int>? {
 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)
  30. fun divide(num: Int, den: Int): Option<Int> {
 return if (num

    % den != 0) {
 None
 } else {
 Some(num / den)
 }
 }
 
 fun division(a: Int, b: Int, c: Int): Option<Pair<Int, Int>> {
 val ac = divide(a, c)
 return when (ac) {
 is Some<Int> -> {
 val bc = divide(b, c)
 when (bc) {
 is Some<Int> -> {
 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
  31. fun division(a: Int, b: Int, c: Int): Option<Pair<Int, Int>> {


    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<Pair<Int, Int>> {
 val ac = divide(a, c)
 return when (ac) {
 is Some<Int> -> {
 val bc = divide(b, c)
 when (bc) {
 is Some<Int> -> {
 Some(ac.get() to bc.get())
 }
 else -> None
 }
 }
 else -> None
 }
 } = replaced by 1st flatMap = replaced by 2nd flatMap
  32. Either An unbiased representation of two possibles return values (left

    or right), commonly used to represent an error or a successful operation
  33. Either II data class User(val name: String)
 
 fun getUser(url:

    String): Either<String, User> { //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())
 }
  34. Either III data class User(val name: String)
 
 fun getUser(url:

    String): Either<Exception, User> = 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())
 }
  35. Disjunction II fun getUser(url: String): Disjunction<Exception, User> = 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())
 }
  36. Disjunction III val user1 = getUser("http: //myapi.com/user/1")
 val user2 =

    getUser("http: //myapi.com/user/2")
 
 val userList: Disjunction<Exception, List<User >> = user1.flatMap { u1 ->
 user2.map { u2 ->
 listOf(u1, u2)
 }
 }
 
 val userList2: Disjunction<List<Exception>, List<User >> = validate(user1,user2){ u1, u2 ->
 listOf(u1, u2)
 }
  37. 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)