Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

The Arrow Library in Kotlin

454 views

Published on

Using the Arrow Library to increase the Functional Programming potential of the Kotlin Language. Presented to the Cork Java Users Group and Cork Functional Programmers on 1st May 2018

Published in: Software
  • Be the first to comment

  • Be the first to like this

The Arrow Library in Kotlin

  1. 1. training@instil.co May 2018 © Instil Software 2018 The Arrow Library in Kotlin Cork JUG / Functional Programmers https://gitlab.com/instil-training/cjug-arrow-2018
  2. 2. Thank You Kats & JUGs For Inviting Me Once Again!
  3. 3. A quick Kotlin refresher • Kotlin compared to Java • Functional coding in Kotlin • Reactive Programming in Kotlin Introducing Arrow • What is it and why does it matter? Arrow Pt1: Data Types • Option, Try, Either and Validated Arrow Pt2: Enhanced FP • Partial Invocation, Currying and Composition Arrow Pt3: Esoteric Stuff • Lenses, IO etc… Agenda For This Talk
  4. 4. • In case this is all new to you… Kotlin Refresher
  5. 5. Kotlin Compared To Java
  6. 6. Enter some numbers or three 'X' to finish 10 20 30 40 50 XXX Total of numbers is: 150 Enter some numbers or three 'X' to finish wibble Ignoring wibble 12 13 14 XXX Total of numbers is: 39 Enter some numbers or three 'X' to finish XXX Total of numbers is: 0
  7. 7. public class Program { public static void main(String[] args) { @SuppressWarnings("resource") Scanner scanner = new Scanner(System.in); List<Integer> numbers = new ArrayList<>(); System.out.println("Enter some numbers or three 'X' to finish"); Pattern endOfInput = Pattern.compile("[X]{3}"); while(scanner.hasNextLine()) { if(scanner.hasNextInt()) { numbers.add(scanner.nextInt()); } else { if(scanner.hasNext(endOfInput)) { break; } else { String mysteryText = scanner.nextLine(); System.out.printf(“Ignoring %sn", mysteryText); } } } int total = numbers.stream().reduce((a,b) -> a + b).orElse(0); System.out.printf("Total of numbers is: %sn",total); } } A Sample Java Program
  8. 8. fun main(args: Array<String>) { val numbers = mutableListOf<Int>() val scanner = Scanner(System.`in`) val endOfInput = Regex("[X]{3}") println("Enter some numbers or three 'X' to finish") while (scanner.hasNextLine()) { if (scanner.hasNextInt()) { numbers += scanner.nextInt() } else { if (scanner.hasNext(endOfInput.toPattern())) { break } else { val mysteryText = scanner.nextLine() println("Ignoring $mysteryText") } } } val total1 = numbers.fold(0,Int::plus) println("Total of numbers is: $total1") //simpler alternative in this case val total2 = numbers.sum() println("Total of numbers is: $total2") } The Sample Re-Written in Kotlin Points to note:  No redundant class  No semi-colons  Type inference  Both ‘val’ and ‘var’  Helper functions  String interpolation  Simplified collections  Interop with Java types  Simpler use of FP
  9. 9. Functional Programming
  10. 10. fun <T,U>map(input: List<T>, mappingFunc: (T)->U): List<U> { val results = ArrayList<U>() for (item in input) { results.add(mappingFunc(item)); } return results } fun <T>filter(input: List<T>, filterFunc: (T)->Boolean): List<T> { val results = ArrayList<T>() for (item in input) { if (filterFunc(item)) { results.add(item); } } return results } fun <T>partition(input: List<T>, filterFunc: (T)->Boolean): Pair<List<T>,List<T>> { val results = Pair(ArrayList<T>(),ArrayList<T>()) for (item in input) { if (filterFunc(item)) { results.first.add(item); } else { results.second.add(item); } } return results } Implementing Filter, Map and Partition
  11. 11. fun main(args: Array<String>) { val originalData = arrayListOf<String>("12","34","56","78","90") val mappedData = map(originalData, String::toInt) val filteredData = filter(mappedData) { it > 50 } val partitionedData = partition(mappedData) { it > 50 } printResults("Results of mapping",mappedData) printResults("Results of filtering", filteredData) printResults("Results of partitioning", partitionedData) } fun<T> printResults(title: String, input: List<T>) { println("----- $title -----") for(item in input) { println("t$item") } } fun<T,U> printResults(title: String, input: Pair<List<T>,List<U>>) { println("----- $title -----") println("tfirst items in pair") for(item in input.first) { println("tt$item") } println("tsecond items in pair") for(item in input.second) { println("tt$item") } } Implementing Filter, Map and Partition
  12. 12. Implementing Filter, Map and Partition ----- Results of mapping ----- 12 34 56 78 90 ----- Results of filtering ----- 56 78 90 ----- Results of partitioning ----- first items in pair 56 78 90 second items in pair 12 34
  13. 13. fun wrapInH1(input: String) = "<h1>$input</h1>" fun wrapInH2(input: String) = "<h2>$input</h2>" fun wrapInTag(tagName: String, input: String): String { val openingTag = "<$tagName>" val closingTag = "</$tagName>" return "$openingTag$input$closingTag" } fun buildWrapper(tagName: String): (String) -> String { val openingTag = "<$tagName>" val closingTag = "</$tagName>" return { "$openingTag$it$closingTag" } } Using Functions as Outputs
  14. 14. fun main(args: Array<String>) { println(wrapInH1("Marge")) println(wrapInH2("Homer")) println(wrapInTag("h3","Liza")) println(wrapInTag("h4","Bart")) val wrapInBold = buildWrapper("b") val wrapInItalics = buildWrapper("i") val wrapInMark = buildWrapper("mark") println(wrapInBold("Maggie")) println(wrapInItalics("Snowball III")) println(wrapInMark("Santas Little Helper")) } <h1>Marge</h1> <h2>Homer</h2> <h3>Liza</h3> <h4>Bart</h4> <b>Maggie</b> <i>Snowball III</i> <mark>Santas Little Helper</mark> Using Functions as Outputs
  15. 15. Reactive Programming
  16. 16. fun main(args: Array<String>) { val loremIpsum = """Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.""" val flux = Flux.fromIterable(loremIpsum.split(" ")) flux.map { it.toLowerCase() } .flatMap { word -> Flux.fromArray(word.toCharArray() .toTypedArray()) } .filter { theChar -> Character.isLetter(theChar) } .collectMultimap { it } .map { table -> table.mapValues { entry -> entry.value.size } } .subscribe(::printTable) } Using Project Reactor To Process In-Memory Data
  17. 17. fun printTable(input: Map<Char, Int>) { println("The frequency count of letters in 'lorem ipsum' is:") input.forEach { entry -> val msgWord = "instance" + if (entry.value > 1) "s" else "" println("${entry.value} $msgWord of ${entry.key}") } } The frequency count of letters in 'lorem ipsum' is: 29 instances of a 3 instances of b 16 instances of c 19 instances of d 38 instances of e 3 instances of f 3 instances of g 1 instance of h 42 instances of i 22 instances of l 17 instances of m 24 instances of n 29 instances of o 11 instances of p ... Using Reactor To Process In-Memory Data
  18. 18. • What is it? • Why does it matter? Introducing Arrow
  19. 19. Languages Today Are Converging
  20. 20. The Great Scala vs. Kotlin Debate I am an Enterprise Coder. Like my father before me You fool. If only you knew the power of Monads!
  21. 21. How Much Functional Programming is ‘Just Right’?
  22. 22. One Possible Solution…
  23. 23. Arrow is a functional programming library for Kotlin coders • Launched in Jan when the two leading libraries joined focus • To prevent the ‘Scalaz vs. Cats’ debate that exists in Scala It is not a formal part of the Kotlin environment • But is generating a lot of interest in the Kotlin community • It has resulted in proposals for changes to the language Learning Arrow can be a bit frustrating as: • The documentation is incomplete and patchy • Changes are still occurring between releases • Sample code is hard to find…. I’m still learning what its all about! • This is very much a report on my progress so far… Introducing Arrow
  24. 24. Part One: Data Types
  25. 25. Lets Do The Same Thing Many Times 
  26. 26. Option
  27. 27. fun readPropertyA(name: String): Option<String> { val result = System.getProperty(name) return if(result != null) Some(result) else None } fun readPropertyB(name: String) = Option.fromNullable(System.getProperty(name)) Reading Property Values Safely Via Option
  28. 28. fun print1(input: Option<String>): Unit { when(input) { is None -> println("Nothing was found") is Some -> println("'${input.t}' was found") } } fun print2(input: Option<String>): Unit { println("${input.getOrElse { "Nothing" }} was found") } Reading Property Values Safely Via Option
  29. 29. fun print3(input: Option<String>): Unit { val result = input.fold({ "Nothing" }, { it }) println("$result was found") } fun print4(input1: Option<String>, input2: Option<String>): Unit { val result = input1.flatMap { first -> input2.map { second -> "$first and $second" } } println("Results are ${result.getOrElse { "Nothing" }}") } Reading Property Values Safely Via Option
  30. 30. fun main(args: Array<String>) { print1(readPropertyA("java.version")) print1(readPropertyA("wibble")) printLine() print2(readPropertyB("java.version")) print2(readPropertyB("wibble")) printLine() print3(readPropertyA("java.version")) print3(readPropertyA("wibble")) printLine() print4( readPropertyA("java.version"), readPropertyB("java.version") ) printLine() print4( readPropertyA("java.version"), readPropertyB("wibble") ) printLine() print4(readPropertyA("wibble"), readPropertyB("wibble")) printLine() print4(readPropertyA("wibble"), readPropertyB("wibble")) } Reading Property Values Safely Via Option
  31. 31. Reading Property Values Safely Via Option '1.8.0_121' was found Nothing was found --------------- 1.8.0_121 was found Nothing was found --------------- 1.8.0_121 was found Nothing was found --------------- Results are 1.8.0_121 and 1.8.0_121 --------------- Results are Nothing --------------- Results are Nothing --------------- Results are Nothing
  32. 32. class Postcode(val input: String?) { fun value() = Option.fromNullable(input) } class Address(val street: String, val postcode: Postcode?) { fun location() = Option.fromNullable(postcode) } class Person(val name: String, val address: Address?) { fun residence() = Option.fromNullable(address) } Using Option as a Monad
  33. 33. fun printPostcode(person: Person) { val result = Option.monad().binding { val address = person.residence().bind() val location = address.location().bind() location.value().bind() }.fix() println(result.fold( { "No postcode available" }, { "Postcode of $it" })) } Using Option as a Monad
  34. 34. fun main(args: Array<String>) { printPostcode(Person("Dave", Address("10 Arcatia Road", Postcode("ABC 123")))) printPostcode(Person("Dave", Address("10 Arcatia Road", null))) printPostcode(Person("Dave", null)) } Using Option as a Monad Postcode of ABC 123 No postcode available No postcode available
  35. 35. Try
  36. 36. fun firstLine(path: String): Try<String> { fun readFirstLine(path: String): String { val reader = BufferedReader(FileReader(path)) return reader.use { it.readLine() } } return Try { readFirstLine(path) } } Reading the First Line from a File via Try
  37. 37. fun print1(input: Try<String>): Unit { when(input) { is Success -> println("Read '${input.value}'") is Failure -> println("Threw '${input.exception.message}'") } } fun print2(input: Try<String>): Unit { val result = input.fold( { "Threw '${it.message}'" }, { "Read '$it'" }) println(result) } fun print3(input: Try<String>): Unit { input.map { println("Read '$it'") } input.recover { println("Threw '${it.message}'") } } Reading the First Line from a File via Try
  38. 38. Lets Have Some Fun 
  39. 39. fun print4(input: String) { fun fullPath(str: String) = "data/$str" val finalResult = firstLine(fullPath(input)).flatMap { one -> firstLine(fullPath(one)).flatMap { two -> firstLine(fullPath(two)).flatMap { three -> firstLine(fullPath(three)).flatMap { four -> firstLine(fullPath(four)).map { result -> result } } } } } val message = finalResult.fold({ it.message }, { it }) println("Path navigation produced '$message'") } Reading the First Line from a File via Try
  40. 40. fun main(args: Array<String>) { print1(firstLine("data/input4.txt")) print1(firstLine("foobar.txt")) printLine() print2(firstLine("data/input4.txt")) print2(firstLine("foobar.txt")) printLine() print3(firstLine("data/input4.txt")) print3(firstLine("foobar.txt")) printLine() print4("input.txt") print4("foobar.txt") } Reading the First Line from a File via Try
  41. 41. Read 'Fortune favors the prepared mind' Threw 'foobar.txt (No such file or directory)' --------------- Read 'Fortune favors the prepared mind' Threw 'foobar.txt (No such file or directory)' --------------- Read 'Fortune favors the prepared mind' Threw 'foobar.txt (No such file or directory)' --------------- Path navigation produced 'Fortune favors the prepared mind' Path navigation produced 'data/foobar.txt (No such file or directory)'
  42. 42. fun readFromFiles(input: String): String? { val result = Try.monad().binding { val one = firstLine(fullPath(input)).bind() val two = firstLine(fullPath(one)).bind() val three = firstLine(fullPath(two)).bind() val four = firstLine(fullPath(three)).bind() firstLine(fullPath(four)).bind() }.fix() return result.fold({ it.message }, { it }) } Reading the First Line from a File via Try
  43. 43. fun main(args: Array<String>) { println("Path navigation produced '${readFromFiles("input.txt")}'") println("Path navigation produced '${readFromFiles("foobar")}'") } Path navigation produced 'Fortune favors the prepared mind' Path navigation produced 'data/foobar (No such file or directory)'
  44. 44. Either
  45. 45. fun genNumber() : Either<Int, Int> { val number = (random() * 100).toInt() return if(number % 2 == 0) Right(number) else Left(number) } fun main(args: Array<String>) { val results = (1 .. 10).map { genNumber().flatMap { first -> genNumber().map { second -> Pair(first, second) } } } results.forEach { result -> val msg = result.fold( { "Odd number $it" }, { "Even numbers ${it.first} and ${it.second}" } ) println(msg) } } Using the Either Type Even numbers 50 and 40 Odd number 77 Even numbers 52 and 32 Odd number 25 Odd number 89 Even numbers 80 and 54 Odd number 65 Odd number 1 Odd number 1 Odd number 33
  46. 46. Validated
  47. 47. Our Sample Problem Whats your ID? ab12 How old are you? 19 Where do you work? HR Error: Bad ID Whats your ID? AB12 How old are you? 19 Where do you work? IT Result: AB12 of age 19 working in IT Whats your ID? ab12 How old are you? 14 Where do you work? Mars Error: Bad Dept Bad ID Bad Age
  48. 48. class Employee(val id: String, val age: Int, val dept: String) { override fun toString() = "$id of age $age working in $dept" } fun askQuestion(question: String): String { println(question) return readLine() ?: "" } Reading and Validating Information
  49. 49. fun checkID(): Validated<String, String> { val regex = Regex("[A-Z]{2}[0-9]{2}") val response = askQuestion("Whats your ID?") return if(regex.matches(response)) Valid(response) else Invalid("Bad ID") } fun checkAge(): Validated<String, Int> { val response = askQuestion("How old are you?").toInt() return if(response > 16) Valid(response) else Invalid("Bad Age") } fun checkDept(): Validated<String, String> { val depts = listOf("HR", "Sales", "IT") val response = askQuestion("Where do you work?") return if(depts.contains(response)) Valid(response) else Invalid("Bad Dept") } Reading and Validating Information
  50. 50. fun main(args: Array<String>) { val sg = object : Semigroup<String> { override fun String.combine(b: String) = "$this $b" } val id = checkID() val age = checkAge() val dept = checkDept() val result = Validated.applicative(sg) .map(id, age, dept, { (a,b,c) -> Employee(a,b,c) }) .fix() println(result.fold({ "Error: $it" }, {"Result: $it"} )) } Reading and Validating Information
  51. 51. Part Two: Functions
  52. 52. Lets Play With Plugging Functions Together…
  53. 53. Partial Invocation
  54. 54. fun demo1() { val addNums = { no1: Int, no2: Int -> println("Adding $no1 to $no2") no1 + no2 } val addSeven = addNums.partially2(7) val result = addSeven(3) println(result) } Partial Invocation Adding 3 to 7 10
  55. 55. fun demo2() { val addNums = { no1: Int, no2: Int -> println("Adding $no1 to $no2") no1 + no2 } val addSeven = addNums.partially2(7) val addSevenToThree = addSeven.partially1(3) val result = addSevenToThree() println(result) } Partial Invocation Adding 3 to 7 10
  56. 56. fun demo3() { val addNums = { no1: Int, no2: Int -> println("Adding $no1 to $no2") no1 + no2 } val addSeven = addNums.reverse().partially2(7) val result = addSeven(3) println(result) } Partial Invocation Adding 7 to 3 10
  57. 57. fun grep(path: String, regex: Regex, action: (String) -> Unit) { val reader = BufferedReader(FileReader(path)) reader.use { it.lines() .filter { regex.matches(it) } .forEach(action) } } val grepLambda = { a: String, b: Regex, c: (String) -> Unit -> grep(a, b, c) } fun printLine() = println("-------------") Here’s Something More Useful
  58. 58. val filePath = "data/grepInput.txt" val regex = "[A-Z]{2}[0-9]{2}".toRegex() grep(filePath, regex, ::println) printLine() grepLambda(filePath, regex, ::println) printLine() Here’s Something More Useful AB12 CD34 EF56 ------------- AB12 CD34 EF56 -------------
  59. 59. val grepAndPrint = grepLambda.partially3(::println) grepAndPrint(filePath, regex) printLine() val sb = StringBuilder() val grepAndConcat = grepLambda.partially3 {sb.append(it)} grepAndConcat(filePath, regex) println(sb.toString()) printLine() val grepAndPrintRegex = grepAndPrint.partially2(regex) grepAndPrintRegex(filePath) Here’s Something More Useful AB12 CD34 EF56 ------------- AB12CD34EF56 ------------- AB12 CD34 EF56
  60. 60. Currying
  61. 61. val addThree = { a:Int, b: Int, c:Int -> println("Adding $a, $b and $c") a + b + c } fun printLine() = println("--------------------") fun main(args: Array<String>) { println(addThree(10,20,40)) printLine() val f1 = addThree.curried() val f2 = f1(10) val f3 = f2(20) val result = f3(40) println(result) printLine() println(f1(10)(20)(40)) printLine() val f4 = addThree.reverse().curried() println(f4(10)(20)(40)) println() } The Basic Syntax of Currying Adding 10, 20 and 40 70 -------------------- Adding 10, 20 and 40 70 -------------------- Adding 10, 20 and 40 70 -------------------- Adding 40, 20 and 10 70
  62. 62. fun grep(path: String, regex: Regex, action: (String) -> Unit) { val reader = BufferedReader(FileReader(path)) reader.use { it.lines() .filter { regex.matches(it) } .forEach(action) } } val grepLambda = { a: String, b: Regex, c: (String) -> Unit -> grep(a,b,c) } fun printLine() = println("-------------") A More Useful Example
  63. 63. fun main(args: Array<String>) { val filePath = "data/grepInput.txt" val regex1 = "[A-Z]{2}[0-9]{2}".toRegex() val regex2 = "[a-z]{2}[0-9]{2}".toRegex() val f1 = grepLambda.curried() val grepInFile = f1(filePath) val grepRegex1 = grepInFile(regex1) val grepRegex2 = grepInFile(regex2) grepRegex1(::println) printLine() grepRegex2(::println) } A More Useful Example AB12 CD34 EF56 ------------- ab12 cd34 ef56
  64. 64. Composition
  65. 65. val source = { name: String -> "data/$name" } val allLines = { path: String -> val reader = BufferedReader(FileReader(path)) reader.use { it.lines().toList() } } val findMatches = { input: List<String> -> val regex = "[A-Z]{2}[0-9]{2}".toRegex() input.filter(regex::matches) } Composing Functions Together
  66. 66. fun main(args: Array<String>) { val composed1 = findMatches compose allLines compose source println(composed1("grepInput.txt")) val composed2 = source forwardCompose allLines forwardCompose findMatches println(composed2("grepInput.txt")) val composed3 = source andThen allLines andThen findMatches println(composed3("grepInput.txt")) } Composing Functions Together [AB12, CD34, EF56] [AB12, CD34, EF56] [AB12, CD34, EF56]
  67. 67. Part 3: Weird Stuff
  68. 68. Lenses
  69. 69. @optics data class Postcode(val value: String) { override fun toString() = "$value" } @optics data class Address(val street: String, val postcode: Postcode) { override fun toString() = "$street ($postcode)" } @optics data class Person(val name: String, val address: Address) { override fun toString() = "$name living at $address" } Copying Immutable Structures With Lenses
  70. 70. fun main(args: Array<String>) { val oldPerson = Person("Dave", Address("10 Arcatia Road", Postcode("BT26 ABC"))) println(oldPerson) val personAddressPostcode = personAddress() compose addressPostcode() compose postcodeValue() val newPerson = personAddressPostcode.modify(oldPerson, { _ -> "BT37 DEF" }) println(newPerson) } Copying Immutable Structures With Lenses Dave living at 10 Arcatia Road (BT26 ABC) Dave living at 10 Arcatia Road (BT37 DEF)

×