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
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 }
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
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
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) { 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()) }
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)
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 = 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")
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)
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
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
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()) }
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()) }
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()) }