fun() Talk: Exploring Kotlin Functions

fun() Talk: Exploring Kotlin Functions

Df78ed2e647f52dadaac996721a6bdfb?s=128

Nate Ebel

March 27, 2018
Tweet

Transcript

  1. fun() Talk Exploring Kotlin Functions @n8ebel

  2. Kotlin ♥ Functions @n8ebel #droidconbos 2

  3. Easy to Get Started • Can use the IDE conversion

    tool • Can try online (https://try.kotlinlang.org) • Easy to transfer existing knowledge @n8ebel #droidconbos 3
  4. Flexible & Convenient • Parameter & type flexibility • Variations

    in scoping • Variety of modifiers @n8ebel #droidconbos 4
  5. Freedom to Reimagine The flexibility & functionality of functions allow

    us to break away from traditional java conventions and reimagine how we build our projects @n8ebel #droidconbos 5
  6. Let's Have Some fun() @n8ebel #droidconbos 6

  7. From Methods to Functions @n8ebel #droidconbos 7

  8. Hello Java Method void helloFunctions() { System.out.println("Yay, Functions"); } •

    return type • name • method body @n8ebel #droidconbos 8
  9. java method converted to kotlin fun helloFunctions() { println("Yay, Functions")

    } • adds the fun keyword • no explicit return type • same name @n8ebel #droidconbos 9
  10. Further Simplification fun helloFunctions() = println("Yay, Functions") @n8ebel #droidconbos 10

  11. So What's Left? Seems pretty straightforward, what else is there

    to know? @n8ebel #droidconbos 11
  12. A Lot @n8ebel #droidconbos 12

  13. Let's Build On What We Know @n8ebel #droidconbos 13

  14. Parameter & Type Freedom • Default parameters • Named parameters

    • Return types • when can we omit? • when can we infer? • Generic functions @n8ebel #droidconbos 14
  15. Parameters fun helloFunctions(excitingThing:String) { println("Yay, " + excitingThing) } helloFunctions("functions")

    // outputs "Yay, functions" @n8ebel #droidconbos 15
  16. Parameters fun helloFunctions(exclamation:String, excitingThing:String) { println(exclamation + ", " +

    excitingThing) } helloFunctions("Yay", "functions") // outputs "Yay, functions" @n8ebel #droidconbos 16
  17. Now It Gets Interesting @n8ebel #droidconbos 17

  18. Default Parameter Values fun helloFunctions(exclamation:String, excitingThing:String = "functions") { println(exclamation

    + ", " + excitingThing) } helloFunctions("Yay", "functions") // outputs "Yay, functions" helloFunctions("Yay") // outputs "Yay, functions" @n8ebel #droidconbos 18
  19. Function parameters can have default values, which are used when

    a corresponding argument is omitted @n8ebel #droidconbos 19
  20. Default Parameter Values • allows us the flexibility of overloads

    without the verbosity of writing them • help document the function contract by indicating what "sensible defaults" might be @n8ebel #droidconbos 20
  21. Default Parameters & Java Java doesn't have default parameter values

    • must specific all parameter values when calling from Java • can use @JvmOverloads to generate overloads for each parameter • generated overloads will used the specified default values @n8ebel #droidconbos 21
  22. Named Arguments Improve readability of function invocations helloFunctions("functions", "functions") •

    How do we know which value is correct? @n8ebel #droidconbos 22
  23. Named Arguments Much easier to understand with named arguments helloFunctions(exclamation

    = "yay!", excitingThing = "functions") @n8ebel #droidconbos 23
  24. Named Arguments Modify order of passed parameters by using named

    arguments fun helloFunctions(exclamation:String, excitingThing:String = "functions") { println(exclamation + ", " + excitingThing) } helloFunctions("Hooray", "functions") helloFunctions("Hooray") helloFunctions(excitingThing = "functions", exclamation = "Hooray") // all output "Hooray, functions" @n8ebel #droidconbos 24
  25. Named Arguments There are limitations to how named & positioned

    arguments are used - once an argument name is specificed, all subsequent arguments must be named as well @n8ebel #droidconbos 25
  26. Named Arguments helloFunctions("hooray", "Droidcon Boston") helloFunctions("hooray", excitingThing = "Droidcon Boston")

    // both output "hooray, Droidcon Boston" helloFunctions(excitingThing = "Droidcon Boston", "hooray") // error: Mixing named and positioned arguments not allowed @n8ebel #droidconbos 26
  27. Variable Number of Arguments We can define a parameter to

    accept a variable number of arguments T - use the vararg keyword - the vararg param is then treated as an array of type T - default value must now be an array @n8ebel #droidconbos 27
  28. Variable Number of Arguments fun helloFunctions(exclamation:String, vararg excitingThings:String) { for(excitingThing

    in excitingThings) { println(exclamation + ", " + excitingThing) } } helloFunctions("yay!", "Droidcon Boston", "Kotlin", "Android") // outputs: // yay!, Droidcon Boston // yay!, Kotlin // yay!, Android @n8ebel #droidconbos 28
  29. Variable Number of Arguments Typically, a vararg parameter will be

    the last one Can be used in any order if: - other parameters are called using named argument syntax - last parameter is a function passed outside the parentheses @n8ebel #droidconbos 29
  30. Variable Number of Arguments This works great helloFunctions("yay!", "Droidcon Boston",

    "Kotlin", "Android") helloFunctions("Droidcon Boston", "Kotlin", "Android", exlamation = "yay!") // both output: // yay!, Droidcon Boston // yay!, Kotlin // yay!, Android @n8ebel #droidconbos 30
  31. Variable Number of Arguments This works helloFunctions("Droidcon Boston", "Kotlin", "Android")

    // output: // "Droidcon Boston, Kotlin" // "Droidcon Boston, Android" @n8ebel #droidconbos 31
  32. Variable Number of Arguments This won't compile helloFunctions("Droidcon Boston", exlamation

    = "yay!", "Kotlin", "Android") // error: "no matching function" @n8ebel #droidconbos 32
  33. Variable Number of Arguments Use "spread" operator to pass an

    existing array of values val thingsToBeExcitedAbout = arrayOf("Droidcon Boston", "Kotlin", "Android") helloFunctions("yay!", *thingsToBeExcitedAbout) // output: // yay!, Droidcon Boston // yay!, Kotlin // yay!, Android @n8ebel #droidconbos 33
  34. Variable Number of Arguments "Spreading" can be used alone, or

    with other passed varargs as well helloFunctions("yay!", "coffee", *thingsToBeExcitedAbout) helloFunctions("yay!", *thingsToBeExcitedAbout, "coffee") • input array to the vararg parameter is handled in order @n8ebel #droidconbos 34
  35. Return Types What is the return type? fun helloFunctions(exclamation:String, excitingThing:String="functions")

    { println(exclamation + ", " + excitingThing) } @n8ebel #droidconbos 35
  36. If a function does not return any useful value, its

    return type is Unit @n8ebel #droidconbos 36
  37. Return Types These are equivalent fun helloFunctions(exclamation:String, excitingThing:String="functions") : Unit

    { println(exclamation + ", " + excitingThing) } fun helloFunctions(exclamation:String, excitingThing:String="functions") { println(exclamation + ", " + excitingThing) } @n8ebel #droidconbos 37
  38. Return A Non-Unit Type Functions with block body require explicit

    return type & call for non-Unit functions fun helloFunctions(exclamation:String, excitingThing:String="functions") : String { return exclamation + ", " + excitingThing } @n8ebel #droidconbos 38
  39. Return A Non-Unit Type Can infer return type for single-expression

    functions fun helloFunctions(exclamation:String, excitingThing:String="functions") = exclamation + ", " + excitingThing @n8ebel #droidconbos 39
  40. Generic Functions Like classes, functions may have generic type parameters

    public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> { return filterTo(ArrayList<T>(), predicate) } public fun <T> Iterable<T>.toHashSet(): HashSet<T> { return toCollection(HashSet<T>(mapCapacity(collectionSizeOrDefault(12)))) } listOf(2,4,6,8).filter{ ... } setOf(2,4,6).toHashSet() @n8ebel #droidconbos 40
  41. concise convenient flexible @n8ebel #droidconbos 41

  42. What Next? @n8ebel #droidconbos 42

  43. Let's explore some variations on how you can create and

    use functions @n8ebel #droidconbos 43
  44. Variations In Scope • Top-level • Member functions • Local

    • CompanionObject • Extension functions @n8ebel #droidconbos 44
  45. Top-Level functions • Not tied to a class • Defined

    within a Kotlin file • Belong to their declared file's package • Import to use within other packages @n8ebel #droidconbos 45
  46. Top-Level Function Patterns • Replace stateless classes filled with static

    methods • Swap your "Util" or "Helper" classes with functions @n8ebel #droidconbos 46
  47. Top-Level Function Considerations • Not truly removing classes • Generated

    as a public static method on a class using a special convention • <function's file name>Kt.java @n8ebel #droidconbos 47
  48. Top-Level Function Considerations Inside Logging.kt package logging fun log(error:Throwable) {...}

    @n8ebel #droidconbos 48
  49. Top-Level Function Considerations Call from Kotlin log(Throwable("oops")) @n8ebel #droidconbos 49

  50. Top-Level Function Considerations Generated Code public class LoggingKt { public

    static void log(Throwable error) {...} } @n8ebel #droidconbos 50
  51. Top-Level Function Considerations Call from Java LoggingKt.log(new Throwable("oops")) @n8ebel #droidconbos

    51
  52. Top-Level Function Considerations Can override the generated class/file name •

    Add @file:JvmName(<desired class name>) to function's file • Must be before the declared package @n8ebel #droidconbos 52
  53. Top-Level Function Considerations Inside Logging.kt @file:JvmName("LoggingFunctions") package logging fun log(error:Throwable)

    {...} @n8ebel #droidconbos 53
  54. Top-Level Function Considerations Call from Java LoggingFunctions.log(new Throwable("oops")) @n8ebel #droidconbos

    54
  55. Top-Level Function Summary • Function declared in a file outside

    of any class • Can replace stateless helper/util classes • Can override generated class name to improve Java interop @n8ebel #droidconbos 55
  56. Member Functions • Function on a class or object •

    Like a Java method • Have access to private members of the class or object @n8ebel #droidconbos 56
  57. Member Functions class Speaker() { fun giveTalk() { ... }

    } // create instance of class Speaker and call giveTalk() Speaker().giveTalk() @n8ebel #droidconbos 57
  58. Member Function Considerations • Default arguments can't be changed in

    overridden methods • If overriding a method, you must omit the default values @n8ebel #droidconbos 58
  59. Local Functions Functions inside of functions • Create a function

    that is scoped to another function • Useful if your function is only ever called from another function @n8ebel #droidconbos 59
  60. Local Functions • Declare like any other function, just within

    a function • Have access to all params and variables of the enclosing function @n8ebel #droidconbos 60
  61. Local Functions Why would you want this? • Clean code

    • Avoids code duplication • Avoid deep chains of function calls @n8ebel #droidconbos 61
  62. Local Functions fun parseAccount(response:AccountResponse) : Account { ... val hobbies

    = response.getField("hobbies").map{ val key = it.getField("key") Hobby(key) } val favoriteFoods = response.getField("favorite_foods").map{ val key = it.getField("key") Food(key) } } @n8ebel #droidconbos 62
  63. Local Functions fun parseAccount(response:AccountResponse) : Account { fun parseKey(entity:ResponseEntity) =

    entity.getField("key") ... val hobbies = response.getField("hobbies").map{ val key = parseKey(it) Hobby(key) } val favoriteFoods = response.getField("favorite_foods").map{ val key = parseKey(it) Food(key) } } @n8ebel #droidconbos 63
  64. Local Function Considerations • Local function or private method? •

    Is the logic going to be needed outside the current function? • Does the logic need to be tested in isolation? • Is the enclosing function still readable? @n8ebel #droidconbos 64
  65. Companion Objects • No static method/functions in Kotlin • Recommended

    to use top-level functions instead • What if you need access to private members of an object? @n8ebel #droidconbos 65
  66. Companion Objects • Want to create a factory method? •

    Define a member function on a companion object to gain access to private members/constructors @n8ebel #droidconbos 66
  67. Companion Objects class Course private constructor(val key:String) // won't work

    // can't access the private constructor fun createCourse(key:String) : Course { return Course(key) } @n8ebel #droidconbos 67
  68. Companion Objects class Course private constructor(val key:String) { companion object

    { fun createCourse(key:String) : Course { return Course(key) } } } // can then call the factory method Course.createCourse("somekey") @n8ebel #droidconbos 68
  69. Companion Object Function Considerations Java usage is ugly // from

    Java Course.Companion.createCourse("somekey") @n8ebel #droidconbos 69
  70. Companion Object Function Considerations class Course private constructor(val key:String) {

    companion object Factory { fun createCourse(key:String) : Course { return Course(key) } } } // from Java Course.Factory.createCourse("somekey") @n8ebel #droidconbos 70
  71. different scopes for different use cases @n8ebel #droidconbos 71

  72. Variations @n8ebel #droidconbos 72

  73. Variations • infix • extension • higher-order • inline @n8ebel

    #droidconbos 73
  74. infix • infix keyword enables usage of infix notation •

    What is infix notation? • Can omit the dot & parentheses for the function call • "key" to "value" @n8ebel #droidconbos 74
  75. infix • Must be a member function or extension function

    • Must take a single, non-varargs, parameter with no default value @n8ebel #droidconbos 75
  76. infix class ConferenceAttendee { infix fun addInterest(name:String){...} } // call

    the function without dot or parentheses val attendee = ConferenceAttendee() attendee addInterest "Kotlin" @n8ebel #droidconbos 76
  77. infix • Provides a very clean, human-readable syntax • Core

    building block of custom DSLs @n8ebel #droidconbos 77
  78. infix "hello" should haveSubstring("ell") "hello" shouldNot haveSubstring("olleh") https://github.com/kotlintest/kotlintest @n8ebel #droidconbos

    78
  79. Extension Functions • Extend the functionality of an existing class

    • Defined outside the class • Used as if they were a member of a class @n8ebel #droidconbos 79
  80. Why Extension Functions? • Clean-up or extend classes & apis

    you don't control • Remove helper classes & simplify top-level functions @n8ebel #droidconbos 80
  81. Extension Functions // add a new function to the View

    class fun View.isVisible() = visibility == View.VISIBLE yourView.isVisible() @n8ebel #droidconbos 81
  82. Extension Functions fun showToast( context: Context, msg:String, duration: Int =

    Toast.LENGTH_SHORT) { Toast.makeText(context, msg, duration).show() } showToast(context, "Toast!") @n8ebel #droidconbos 82
  83. Extension Functions fun Context.showToast( msg: CharSequence, duration: Int = Toast.LENGTH_SHORT)

    { Toast.makeText(this, msg, duration).show() } context.showToast("Toast!") @n8ebel #droidconbos 83
  84. Extension Function Considerations • How are these generated under the

    hood? • How are these called from Java? @n8ebel #droidconbos 84
  85. Extension Function Considerations • Generated as static methods that accept

    the receiver object as it's first argument • Default behavior is to use <filename>Kt.<functionName> @n8ebel #droidconbos 85
  86. Extension Function Considerations // ContextExtensions.kt fun Context.showToast(...) { ... }

    // when called from Java ContextExtensionsKt.showToast(context, "Toast!"); @n8ebel #droidconbos 86
  87. Higher-Order Functions • Functions that take, or return, other functions

    • Can be lambda or function reference • Many examples in the Kotlin standard library apply, also, run @n8ebel #droidconbos 87
  88. Higher-Order Functions • Enable interesting patterns & conventions • Support

    functional programming • Can cleanup setup/teardown patterns such as shared prefs @n8ebel #droidconbos 88
  89. Higher-Order Functions fun getScoreCalculator(level:Level) { return when (level) { Level.EASY

    -> { state:ScoreState -> state.score * 10 } Level.HARD -> { state:ScoreState -> state.score * 5 * state.accuracy } } } @n8ebel #droidconbos 89
  90. Higher-Order Functions val predicate = { number:Int -> number >

    5 } listOf(2,4,6,8).filter(predicate) @n8ebel #droidconbos 90
  91. Higher-Order Functions fun filterTheList(value:Int) = value > 5 listOf(2,4,6,8).filter(::filterTheList) @n8ebel

    #droidconbos 91
  92. Higher-Order Functions If the last parameter of a function is

    a function, you can omit the parentheses listOf(2,4,6,8).filter{ number -> number > 5 } @n8ebel #droidconbos 92
  93. Higher-Order Functions public inline fun <R> synchronized(lock: Any, block: ()

    -> R): R { monitorEnter(lock) try { return block() } finally { monitorExit(lock) } } // call from Kotlin synchronized(database) { database.prePopulate() } @n8ebel #droidconbos 93
  94. Higher-Order Function Performance "Using higher-order functions imposes certain runtime penalties"

    • Extra class created when using lambda • If lambda captures variables, extra object created on each call @n8ebel #droidconbos 94
  95. inline • Helps solve higher-order function performance hits • Body

    of the inlined function is substituted for invocations of the function @n8ebel #droidconbos 95
  96. inline inline fun <T> synchronized(lock: Lock, action: () -> T):

    T { lock.lock() try { return action() } finally { lock.unlock() } } // call from Kotlin synchronized(Lock()) {...} @n8ebel #droidconbos 96
  97. inline // sample usage fun inlineExample(l:Lock) { println("before") synchronized(l) {

    println("action") } println("after") } @n8ebel #droidconbos 97
  98. inline With inline the generated code is equivalent to this

    // resulting code fun inlineExample(l:Lock) { println("before") lock.lock() try { println("action") } finally { lock.unlock() } println("after") } @n8ebel #droidconbos 98
  99. Android Reimagined These features enabled us to rethink how we

    build our apps @n8ebel #droidconbos 99
  100. Fewer Helper Classes • ContextHelper, ViewUtils • Replace with •

    top-level functions • extension functions @n8ebel #droidconbos 100
  101. Less Boilerplate fun doTheThingSafely(theThing:() -> Unit) { try { theThing()

    } catch(error:Throwable) { // handle error } } @n8ebel #droidconbos 101
  102. Upgrade Our Apis Can use extensions, default params, etc to

    cleanup common apis • Now seeing community supported examples of this • Android KTX: https://github.com/android/android-ktx • Anko: https://github.com/Kotlin/anko @n8ebel #droidconbos 102
  103. Android KTX sharedPreferences.edit() .putString("key", "without ktx") .putBoolean("isLessBoilerplate", false) .apply() sharedPreferences.edit

    { putString("key", "with ktx") putBoolean("isLessBoilerplate", true) } @n8ebel #droidconbos 103
  104. Cleaner Syntax fun log(msg:String) {...} inline fun runOnBackgroundThread(action:() -> Unit)

    { ... } • More fluent syntax • Simplify test mocking • Avoids extra classes @n8ebel #droidconbos 104
  105. DSLs val articleBuilder = ArticleBuilder() articleBuilder { title = "This

    is the title" addParagraph { body = "This is the first paragraph body" } addParagraph { body = "This is the first paragraph body" imageUrl = "https://path/to/url" } } • https://proandroiddev.com/kotlin-dsl-everywhere- de2994ef3eb0 @n8ebel #droidconbos 105
  106. DSLs DSL examples • https://github.com/gradle/kotlin-dsl • https://github.com/kotlintest/kotlintest • https://github.com/Kotlin/anko/wiki/Anko-Layouts •

    https://kotlinlang.org/docs/reference/type-safe- builders.html @n8ebel #droidconbos 106
  107. Kotlin functions provide flexibility & freedom in how you build

    your apps @n8ebel #droidconbos 107
  108. Go, and Have fun() • Easy to get started •

    Can build your understanding and usage of functions over time • Enables you to rethink how you build your applications @n8ebel #droidconbos 108
  109. Ready to Learn More? • https://engineering.udacity.com • https://n8ebel.com/tag/kotlin • Udacity

    Course: https://www.udacity.com/course/kotlin-for- android-developers--ud888 • Kotlin In Action @n8ebel #droidconbos 109
  110. Thanks For Coming @n8ebel #droidconbos 110

  111. Let's Continue the Discussion with("n8ebel").apply { Twitter .com Medium Instagram

    Facebook GitHub } @n8ebel #droidconbos 111