Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin from scratch 2 - Functions

Kotlin from scratch 2 - Functions

Do you want to learn Kotlin programming language from scratch? This is the 2nd episode of my simple course, focused on functions and functional programming.

Franco Lombardo

May 18, 2020
Tweet

More Decks by Franco Lombardo

Other Decks in Programming

Transcript

  1. Last episode: the power of lambdas val elapsedTime = measureTimeMillis

    { execute(compilationUnit.main.stmts) } public fun measureTimeMillis(block: () -> Unit): Long { val start = System.currentTimeMillis() block() return System.currentTimeMillis() - start }
  2. Last episode: the power of lambdas val elapsedTime = measureTimeMillis

    { execute(compilationUnit.main.stmts) } public fun measureTimeMillis(block: () -> Unit): Long { val start = System.currentTimeMillis() block() return System.currentTimeMillis() - start } This is a function (lambda expression)
  3. What is a lambda? A lambda is a literal of

    a function type val x: Int = 5 5 is a literal of type Int val f: (Int) -> String = { "The number is $it" } This is a literal of type (Int) -> String (Well, on JVM it’s an instance of Function1<Int, String>)
  4. What is a lambda? A lambda is a literal of

    a function type val x: Int = 5 val f: (Int) -> String = { "The number is $it" } it: implicit name of a single parameter
  5. What is a lambda? A lambda is a literal of

    a function type val x: Int = 5 We can use a more explicit syntax val f: (Int) -> String = { number: Int -> "The number is $number" }
  6. What is a lambda? A lambda is a literal of

    a function type val x: Int = 5 Here is the evaluation of the function (y is a String) val f = { number: Int -> "The number is $number" } val y = f(x)
  7. What is a lambda? A lambda is a literal of

    a function type val x: Int = 5 The function here is unevaluated (z is a function) val f = { number: Int -> "The number is $number" } val z = f
  8. What is a closure? A closure is a lambda that

    accesses its closure, i.e. the variables declared in the outer scope val x: Int = 5 val f = { "The number is $x" } val y = f()
  9. What is a closure? Be careful! Prints The number is

    10 var x = 5 val f = { "The number is $x" } x = 10 println ("${f()}")
  10. The power of lambdas val elapsedTime = measureTimeMillis { execute(compilationUnit.main.stmts)

    } public fun measureTimeMillis(block: () -> Unit): Long { val start = System.currentTimeMillis() block() return System.currentTimeMillis() - start }
  11. The power of lambdas val elapsedTime = measureTimeMillis { execute(compilationUnit.main.stmts)

    } public fun measureTimeMillis(block: () -> Unit): Long { val start = System.currentTimeMillis() block() return System.currentTimeMillis() - start } Higher order function: a function that has a function parameter (or result)
  12. Last episode: the power of lambdas val elapsedTime = measureTimeMillis

    ({ execute(compilationUnit.main.stmts) }) “Explicit” syntax public fun measureTimeMillis(block: () -> Unit): Long { val start = System.currentTimeMillis() block() return System.currentTimeMillis() - start } It’s the last parameter of this function
  13. Last episode: the power of lambdas val elapsedTime = measureTimeMillis

    () { execute(compilationUnit.main.stmts) } public fun measureTimeMillis(block: () -> Unit): Long { val start = System.currentTimeMillis() block() return System.currentTimeMillis() - start } It’s the last parameter of this function, so we can pass it outside the parentheses.
  14. Last episode: the power of lambdas val elapsedTime = measureTimeMillis

    (){ execute(compilationUnit.main.stmts) } public fun measureTimeMillis(block: () -> Unit): Long { val start = System.currentTimeMillis() block() return System.currentTimeMillis() - start } Since it’s the only argument, the parentheses can be omitted entirely:
  15. Types of bodies See sample project: https://github.com/f-lombardo/kotlin-from-scratch fun openDatabase(user: String,

    password: String): Database? = if ("franco" == user && "secret" == password) { sampleDatabase() } else { null } fun openDatabase(user: String, password: String): Database? { if ("franco" == user && "secret" == password) { return sampleDatabase() } return null } Block body Expression body
  16. Named arguments println( openDatabase(user = "franco", "secret") ?.findComposerByName("Giuseppe Verdi") ?.findOperaByYear(1853)

    ?: "No results" ) NO! It doesn’t compile. If you start with named arguments you should go on from there println( openDatabase("as400", user = "franco", password = "secret") ?.findComposerByName("Giuseppe Verdi") ?.findOperaByYear(1853) ?: "No results" ) OK
  17. Composing null returning functions println( openDatabase("franco","secret") ?.findComposerByName("Giuseppe Verdi") ?.findOperaByYear(1853) ?:

    "No results" ) fun Database.findComposerByName(name: String): Composer? = this.composers.firstOrNull { it.name == name } Extension function: we extend the Database class with a new method
  18. Composing null returning functions println( openDatabase("franco","secret") ?.findComposerByName("Giuseppe Verdi") ?.findOperaByYear(1853) ?:

    "No results" ) fun Database.findComposerByName(name: String): Composer? = this.composers.firstOrNull { it.name == name } Composer? → {Composer U null} (sum type) findComposerByName is “total” i.e. it maps each input value in one output value
  19. Composing null returning functions println( openDatabase("franco","secret") ?.findComposerByName("Giuseppe Verdi") ?.findOperaByYear(1853) ?:

    "No results" ) “or else” “and then” Total functions (not throwing exceptions) compose easily
  20. “Good” functions compose easily inline fun Database.findComposerBy(predicate: (Composer) -> Boolean)

    = this.composers.firstOrNull(predicate) fun similarName(name: String): (Composer) -> Boolean = { it.name.contains(name, ignoreCase = true) } fun exactName(name: String): (Composer) -> Boolean = { it.name == name } These functions have another function as a result Higher order function
  21. “Good” functions compose easily println( openDatabase("franco","secret") ?.findComposerBy(similarName("verdi")) ?.findOperaByYear(1853) ?: "No

    results" ) println( openDatabase("franco","secret") ?.findComposerBy(exactName("Giuseppe Verdi")) ?.findOperaByYear(1853) ?: "No results" )
  22. “Good” functions compose easily println( openDatabase("franco","secret") ?.findComposerBy(exactMatchToGiacomoPuccini) ?.findOperaByYear(1853) ?: "No

    results" ) val exactMatchToGiacomoPuccini = exactName("Giacomo Puccini") Functions can be stored in variables
  23. “Good” functions compose easily fun exactMatchToGiuseppeVerdi(composer: Composer): Boolean = composer.name

    == "Giuseppe Verdi" println( openDatabase("franco","secret") ?.findComposerBy(::exactMatchToGiuseppeVerdi) ?.findOperaByYear(1853) ?: "No results" ) Passing a function as a parameter using its name
  24. “Good” functions compose easily You can use functions from the

    standard library too fun exactMatchToGiuseppeVerdi(composer: Composer): Boolean = composer.name == "Giuseppe Verdi" openDatabase("franco","secret") ?.findComposerBy(::exactMatchToGiuseppeVerdi) ?.findOperaByYear(1853) .run(::println)
  25. “Good” functions compose easily Extension function applicable to objects of

    any type: it passes the object to the given function fun exactMatchToGiuseppeVerdi(composer: Composer): Boolean = composer.name == "Giuseppe Verdi" openDatabase("franco","secret") ?.findComposerBy(::exactMatchToGiuseppeVerdi) ?.findOperaByYear(1853) .run(::println)
  26. “Good” functions compose easily Not that good: prints null if

    no result is found fun exactMatchToGiuseppeVerdi(composer: Composer): Boolean = composer.name == "Giuseppe Verdi" openDatabase("franco","secret") ?.findComposerBy(::exactMatchToGiuseppeVerdi) ?.findOperaByYear(1853) .run(::println)
  27. “Good” functions compose easily fun Opera?.displayResult(): Unit = if (this

    == null) { println("No result") } else { println(this) } openDatabase("franco","secret") ?.findComposerBy(::exactMatchToGiuseppeVerdi) ?.findOperaByYear(1930) .run(::displayResult)
  28. “Good” functions compose easily fun Opera?.displayResult(): Unit = if (this

    == null) { println("No result") } else { println(this) } openDatabase("franco","secret") ?.findComposerBy(::exactMatchToGiuseppeVerdi) ?.findOperaByYear(1930) .run(::displayResult) Extension function on a nullable type
  29. “Good” functions compose easily fun Opera?.displayResult(): Unit = if (this

    == null) { println("No result") } else { println(this) } openDatabase("franco","secret") ?.findComposerBy(::exactMatchToGiuseppeVerdi) ?.findOperaByYear(1930) .run(::displayResult) if statements are also expressions. (The result here has type Unit)
  30. Infix methods infix fun Opera?.displayResultTo(printStream: PrintStream): Unit = if (this

    == null) { printStream.println("No result") } else { printStream.println(this) } openDatabase("franco","secret") ?.findComposerBy(exactMatchToGiacomoPuccini) ?.findOperaByYear(1901) displayResultTo System.err
  31. “Good” functions compose easily println( openDatabase("franco","secret") ?.findComposerBy(exactMatchToGiacomoPuccini) ?.operas ?.maxBy (Opera::yearOfComposition)

    ?: "No results" ) Functional API for collections (like map, sortBy, find…) They get function parameters A List
  32. “Good” functions compose easily println( openDatabase("franco","secret") ?.findComposerBy(exactMatchToGiacomoPuccini) ?.operas ?.maxBy (Opera::yearOfComposition)

    ?: "No results" ) Member reference: it’s like a lambda that has the object as a parameter and invokes the method on it A List
  33. More functional APIs for collections println( openDatabase("franco","secret") ?.findComposerBy(exactMatchToGiacomoPuccini) ?.operas ?.sortedBy

    (Opera::yearOfComposition) ?.map (Opera::name) ?: "No results" ) sortedBy doesn’t sort the original collection, but it returns a sorted copy of it: immutable data!
  34. Constructors are functions class OperaHtmlDiv(val opera: Opera) { override fun

    toString(): String = "<div>${opera.name} ${opera.yearOfComposition}</div>" } println( openDatabase("franco","secret") ?.findComposerBy(exactMatchToGiacomoPuccini) ?.operas ?.sortedBy (Opera::yearOfComposition) ?.map (::OperaHtmlDiv) ?: "No results" ) We pass a constructor as a parameter
  35. Do we really need all these checks? println( openDatabase("franco", "secret")

    ?.findComposerBy(exactMatchToGiacomoPuccini) ?.run { operas .sortedBy(Opera::yearOfComposition) .map(::OperaHtmlDiv) } ?: "No results" ) Implicit this
  36. The dark side of lambdas fun Database.findComposerBy(predicate: (Composer) -> Boolean)

    = this.composers.firstOrNull(predicate) A new object is created here openDatabase("franco", "secret") ?.findComposerBy { it.name.toLowerCase().startsWith("v") }
  37. The dark side of lambdas inline fun Database.findComposerBy(predicate: (Composer) ->

    Boolean) = this.composers.firstOrNull(predicate) Inlining the function the compiler doesn’t need a new object openDatabase("franco", "secret") ?.findComposerBy { it.name.toLowerCase().startsWith("v") } See https://www.baeldung.com/kotlin-inline-functions • Use Inline with functions that have a lambda parameter • With inline we can’t pass the lambda around, so it can’t be the result of our higher order function
  38. What is a “good” function? • It is total, i.e.

    it maps each input value in one output value (no exceptions) • It works with immutable data types (it doesn’t modify its parameters) • It does not cause any observable side effects • It returns the same result if given the same arguments (it doesn’t depend on the environment) • So it is referentially transparent, i.e. it can be replaced with its corresponding value without changing the program's behaviour
  39. Good reads Kotlin weekly Great weekly newsletter: http://www.kotlinweekly.net/ Uberto Barbini

    on Medium https://medium.com/@ramtop Kotlin expertise blog by Simon Wirtz https://kotlinexpertise.com/ Florina Muntenescu on Medium https://medium.com/@florina.muntenescu Kotlin blog by JetBrains https://blog.jetbrains.com/kotlin/