Share
Embed
Functional Programming in Kotlin with funKTionale
Talk for the Belarus Functional Programming Conference f(by) 2016 #funcBy
Mario Arias
December 10, 2016
Transcript
49 slides
-
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 -
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 -
What is Kotlin? Statically typed programming language for the JVM,
Android and the browser* *LLVM and iOS coming in the future -
Features •Classes •sealed (ADT), data •Interfaces with body methods •Objects
•Companion objects •Enum •Generics •Named and default parameters -
Features II •Control structures •if else, when •do, while, for
•Expressions •Null Safety •High order functions •Delegates •Extension functions -
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 -
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 } -
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 } -
Functions III fun unless(conditional: Boolean, body: () -> Unit) {
if (!conditional) { body() } } unless(age > 18) { //Do something awesome } Create your own control structures!! -
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) } -
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 -
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" } -
Extension functions fun String.fromMinsk() = "$this, FROM Minsk" println("Hello
world".fromMinsk()) -
Extension functions II operator fun String.invoke(suffix:String) = "$this$suffix" println("This
isn't a function"(" !!")) -
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) -
Combine all the things!!! infix fun (() -> Unit).unless(condition: Boolean)
{ if (!condition) { this() } } { /*do some magic */ } unless (age < 18) one-line unless from Ruby!! -
Function composition A technique to create a new function using
two existing functions. % ps aux | grep java -
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)) } } -
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 -
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 -
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 -
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()) } -
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) -
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 -
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 -
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 -
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")) -
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) -
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 -
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") -
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) -
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 -
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 -
Either An unbiased representation of two possibles return values (left
or right), commonly used to represent an error or a successful operation -
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()) } -
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()) } -
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()) } -
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) } -
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)