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.

Coding in Kotlin with Arrow NIDC 2018

373 views

Published on

Presentation on using the Arrow library for enhanced Functional Programming in the Kotlin Language. Delivered at the Northern Ireland Developer Conference 2018.

Published in: Software
  • You'll notice the difference immediately when you make the switch from working with amateurs to working with professionals. I've been betting with these guys for more than three years and in that time I've made nearly £500,000! That's a life changing amount of money. If I can give you one piece of advice it's this – sign up and sign up NOW! Last time I was one of the last guys to grab a spot before Patrick closed the doors. If I hadn't gotten lucky that day I'd be half a million pounds poorer now and my life would be a hell of a lot different. ✱✱✱ https://bit.ly/391WONw
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Why he ACTS like he's not that into you ●●● https://t.cn/A6yxiW77
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • Be the first to like this

Coding in Kotlin with Arrow NIDC 2018

  1. 1. training@instil.co June 2018 © Instil Software 2018 Coding in Kotlin with Arrow NIDC 2018 https://gitlab.com/instil-training/nidc-arrow-2018
  2. 2. About Me Experienced trainer • 20 years in the trenches • Over 1000 deliveries The day job • Head of Learning at Instil • Coaching, mentoring etc… The night job(s) • Husband and father • Krav Maga Instructor
  3. 3. A quick Kotlin refresher • Kotlin compared to Java 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. • A Quick Language Overview Kotlin Refresher
  5. 5. 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
  6. 6. 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.hasNext(endOfInput)) { break; } else if (scanner.hasNextInt()) { numbers.add(scanner.nextInt()); } 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
  7. 7. 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.hasNext(endOfInput.toPattern())) { break } else if (scanner.hasNextInt()) { numbers += scanner.nextInt() } else { val mysteryText = scanner.nextLine() println("Ignoring $mysteryText") } } //Would be better to use ‘numbers.sum()’ val total = numbers.fold(0, Int::plus) println("Total of numbers is: $total") } 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
  8. 8. • What is it? • Why does it matter? Introducing Arrow
  9. 9. Languages Today Are Converging
  10. 10. 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!
  11. 11. How Much Functional Programming is ‘Just Right’?
  12. 12. One Possible Solution…
  13. 13. Arrow is a functional programming library for Kotlin coders • Launched in Jan when the two leading libraries joined forces • 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
  14. 14. Part One: Data Types
  15. 15. Lets Do The Same Thing Many Times 
  16. 16. Option
  17. 17. 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 <<abstract>> Option Some None Empty SetSet of One
  18. 18. 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
  19. 19. 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
  20. 20. 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
  21. 21. 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
  22. 22. 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
  23. 23. 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
  24. 24. 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
  25. 25. Try
  26. 26. 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 <<abstract>> Try Success Failure Holds ErrorHolds Result
  27. 27. 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
  28. 28. Lets Have Some Fun 
  29. 29. 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'") } Traversing Across Files
  30. 30. 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
  31. 31. Traversing Across Files 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)'
  32. 32. 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 }) } Traversing Across Files
  33. 33. fun main(args: Array<String>) { println("Path navigation produced '${readFromFiles("input.txt")}'") println("Path navigation produced '${readFromFiles("foobar")}'") } Traversing Across Files Path navigation produced 'Fortune favors the prepared mind' Path navigation produced 'data/foobar (No such file or directory)'
  34. 34. Either
  35. 35. fun genNumber() : Either<Int, Int> { val number = (random() * 100).toInt() return if(number % 2 == 0) Right(number) else Left(number) } Using the Either Type <<abstract>> Either Left Right Happy PathAlternative
  36. 36. 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
  37. 37. fun checkNum(number:Int) : Either<Int, Int> { return if(number % 2 == 0) Right(number) else Left(number) } class EitherTest : ShouldSpec() { init { should("be able to detect Right") { checkNum(4).shouldBeRight(4) } should("be able to detect Left") { checkNum(5).shouldBeLeft(5) } } } All Arrow Types are Supported in KotlinTest
  38. 38. Validated
  39. 39. 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
  40. 40. 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 <<abstract>> Validated Invalid Valid ResultMessage
  41. 41. 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
  42. 42. 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
  43. 43. Using Types In Combination
  44. 44. Our example of Validated contained a bug… • We assumed the value given for ‘age’ would be an integer Composing Types Whats your ID? AB12 How old are you? abc Exception in thread "main" java.lang.NumberFormatException: For input string: "abc" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Integer.parseInt(Integer.java:580) at java.lang.Integer.parseInt(Integer.java:615) at arrow.types.validated.ProgramKt.checkAge(Program.kt:22) at arrow.types.validated.ProgramKt.main(Program.kt:37)
  45. 45. fun checkAge(): Validated<String, Int> { val response = askQuestion("How old are you?").toInt() return if(response > 16) Valid(response) else Invalid("Bad Age") } Composing Types
  46. 46. We can solve the problem by composing types together • Our methods should return a Try<Validated<T,U>> Composing Types
  47. 47. fun checkID(): Try<Validated<String, String>> { val regex = Regex("[A-Z]{2}[0-9]{2}") val response = askQuestion("Whats your ID?") return Try { if(regex.matches(response)) Valid(response) else Invalid("Bad ID") } } fun checkAge(): Try<Validated<String, Int>> { val response = Try { askQuestion("How old are you?").toInt() } return response.map({ num -> if(num > 16) Valid(num) else Invalid("Bad Age")}) } Composing Types
  48. 48. fun checkSalary(): Try<Validated<String, Double>> { val response = Try { askQuestion("What is your salary?") .toDouble() } return response.map({ num -> if(num > 15000.0) Valid(num) else Invalid("Bad Salary") }) } fun checkDept(): Try<Validated<String, String>> { val depts = listOf("HR", "Sales", "IT") val response = askQuestion("Where do you work?") return Try { if(depts.contains(response)) Valid(response) else Invalid("Bad Dept") } } Composing Types
  49. 49. fun success(emp: Employee) = println("Created $emp") fun exception(ex: Throwable) = println("Exception occurred - ${ex.message}") fun invalid(msg: String?) = println("Validation error occurred - $msg") class Employee(val id: String, val age: Int, val dept: String, val salary: Double) { constructor(t: Tuple4<String,Int,String,Double>) : this(t.a,t.b,t.c,t.d) override fun toString() = "$id of age $age working in $dept earning $salary" } Composing Types
  50. 50. Composing Types Try<Validated<T,U>> Failure Success<Validated<T,U>> exception() Valid<U> Invalid<T> success() invalid()
  51. 51. fun main(args: Array<String>) { val app = Validated.applicative(object : Semigroup<String> { override fun String.combine(b: String) = "$this $b" }) Try.monad().binding { val id = checkID().bind() val age = checkAge().bind() val dept = checkDept().bind() val salary = checkSalary().bind() app.map(id, age, dept, salary, ::Employee).fix() }.fix().fold( {exception(it)}, {it.fold(::invalid,::success)}) } Composing Types
  52. 52. Composing Types Whats your ID? AB12 How old are you? abc Exception occurred - For input string: "abc" Whats your ID? AB12 How old are you? 21 Where do you work? Sales What is your salary? abc Exception occurred - For input string: "abc" Whats your ID? foo How old are you? 21 Where do you work? Sales What is your salary? 30000.0 Validation error occurred - Bad ID Whats your ID? AB12 How old are you? 21 Where do you work? foo What is your salary? 30000.0 Validation error occurred - Bad Dept
  53. 53. Composing Types Whats your ID? AB12 How old are you? 21 Where do you work? Sales What is your salary? 30000.0 Created AB12 of age 21 working in Sales earning 30000.0
  54. 54. Part Two: Functions
  55. 55. Partial Invocation
  56. 56. 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
  57. 57. 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
  58. 58. 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
  59. 59. 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
  60. 60. 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 -------------
  61. 61. 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
  62. 62. Currying
  63. 63. 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
  64. 64. 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
  65. 65. 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
  66. 66. Composition
  67. 67. 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
  68. 68. 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]
  69. 69. Part 3: Weird Stuff
  70. 70. Lenses
  71. 71. @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
  72. 72. 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)

×