Share
Embed
Functional programming in Kotlin with funKTionale
Function composition, curried functions, partial applied functions and Option type in Kotlin using funKTionale library
Mario Arias
September 23, 2015
Transcript
23 slides
-
Functional programming in Kotlin with funKTionale Mario Arias - Kotlin
developers in Manchester -
Software Engineer at Cake Solutions 10+ years of experience with
JVM technologies Spring certified trainer 4+ years with Scala 2+ years with Kotlin funKTionale KotlinPrimavera RxKotlin original developer and team leader* NOT an expert on functional programming * I hit “Merge” and “Release” buttons -
Introduction Concept Kotlin First class and higher-order functions Yes Pure
functions Yes* Recursion Yes Lazy evaluation Yes* Strong type system Yes* -
Functions // explicit type val add2: (Int) -> Int =
{ i -> i + 2 } // inferred type val add2 = { i:Int -> i + 2 } -
Functional eye for the imperative guy fun factorial(n: Long): Long
{ var result = 1L for (it in 1..n) { result *= it } return result } fun functionalFactorial(n: Long): Long { fun go(n: Long, acc: Long): Long { return if (n <= 0) { acc } else { go(n - 1, n * acc) } } return go(n, 1) } fun tailrecFactorial(n: Long): Long { tailrec fun go(n: Long, acc: Long): Long { return if (n <= 0) { acc } else { go(n - 1, n * acc) } } return go(n, 1) } factorial(20)* 0μs 0,025μs 0,05μs 0,075μs 0,1μs factorial functional tailrec Error Value 0,064 0,092 0,078 0,001 0,003 0,002 0,002 0,003 0,001 *Calculated with JMH, SampleTime mode -
fun fib(n: Long): Long = when (n) { 0L ->
0 1L -> 1 else -> { var a = 0L var b = 1L var c = 0L for (it in 2..n) { c = a + b a = b b = c } c } } fun functionalFib(n: Long): Long { fun go(n: Long, prev: Long, cur: Long): Long { return if (n == 0L) prev else go(n - 1, cur, prev + cur) } return go(n, 0, 1) } fun tailrecFib(n: Long): Long { tailrec fun go(n: Long, prev: Long, cur: Long): Long { return if (n == 0L) prev else go(n - 1, cur, prev + cur) } return go(n, 0, 1) } fib(93)* 0μs 30000μs 60000μs 90000μs 120000μs fib functional tailrec Error Value 110.028 115.192 97.997 0,003 0,012 0,013 0,013 0,012 0,003 *Calculated with JMH, SampleTime mode -
Function composition A technique to create a new function using
two existing functions. % ps aux | grep java -
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 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) -
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 “Don’t stain my null-safe language with your monads” -
No one, never* * For some definitions of “No one” and “never” -
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 operations • map • fold • flatMap • filter
• filterNot • exists • forEach • get • getOrElse • orElse fun getSome(): Option<String> = "kotlin".toOption() fun getNone(): Option<String> = null.toOption() @Test fun option() { val option = getSome() when (option) { is Some<String> -> assertEquals(option.get(), "kotlin") is None -> fail() } val otherOption = getNone() when (otherOption) { is Some<String> -> fail() is None -> assertEquals(otherOption, None) } } @Test fun getOrElse() { assertEquals(getSome().getOrElse { "java" }, "kotlin") assertEquals(getNone().getOrElse { "java" }, "java") } @Test fun orNull() { assertNotNull(getSome().orNull()) assertNull(getNone().orNull()) } @Test fun map() { assertEquals(getSome().map { it.toUpperCase() }.get(), "KOTLIN") assertEquals(getNone().map { it.toUpperCase() }, None) } -
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 division function let me check if two numbers (a,b) are divisible by a third one (c) Even if ugly, this is still possible with -1, exceptions or null -
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 -
fun division(a: Int, b: Int, c: Int): Option<Pair<Int, Int>> {
return divide(a, c).flatMap { ac -> divide(b, c).flatMap { bc -> Pair(ac, bc).toOption() } } } Overloading division fun division(a: Int, b: Int, c: Int, d: Int): Option<Triple<Int, Int, Int>> { return divide(a, d).flatMap { ad -> divide(b, d).flatMap { bd -> divide(c, d).flatMap { cd -> Triple(ad, bd, cd).toOption() } } } }