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

fun() Talk: Exploring Kotlin Functions

fun() Talk: Exploring Kotlin Functions

Nate Ebel

March 27, 2018
Tweet

More Decks by Nate Ebel

Other Decks in Programming

Transcript

  1. fun() Talk
    Exploring Kotlin Functions
    @n8ebel

    View Slide

  2. Kotlin ♥ Functions
    @n8ebel #droidconbos 2

    View Slide

  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

    View Slide

  4. Flexible & Convenient
    • Parameter & type flexibility
    • Variations in scoping
    • Variety of modifiers
    @n8ebel #droidconbos 4

    View Slide

  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

    View Slide

  6. Let's Have Some fun()
    @n8ebel #droidconbos 6

    View Slide

  7. From Methods to
    Functions
    @n8ebel #droidconbos 7

    View Slide

  8. Hello Java Method
    void helloFunctions() {
    System.out.println("Yay, Functions");
    }
    • return type
    • name
    • method body
    @n8ebel #droidconbos 8

    View Slide

  9. java method converted to kotlin
    fun helloFunctions() {
    println("Yay, Functions")
    }
    • adds the fun keyword
    • no explicit return type
    • same name
    @n8ebel #droidconbos 9

    View Slide

  10. Further Simplification
    fun helloFunctions() = println("Yay, Functions")
    @n8ebel #droidconbos 10

    View Slide

  11. So What's Left?
    Seems pretty straightforward, what else is there to know?
    @n8ebel #droidconbos 11

    View Slide

  12. A Lot
    @n8ebel #droidconbos 12

    View Slide

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

    View Slide

  14. Parameter & Type Freedom
    • Default parameters
    • Named parameters
    • Return types
    • when can we omit?
    • when can we infer?
    • Generic functions
    @n8ebel #droidconbos 14

    View Slide

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

    View Slide

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

    View Slide

  17. Now It Gets Interesting
    @n8ebel #droidconbos 17

    View Slide

  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

    View Slide

  19. Function parameters can
    have default values, which
    are used when a
    corresponding argument
    is omitted
    @n8ebel #droidconbos 19

    View Slide

  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

    View Slide

  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

    View Slide

  22. Named Arguments
    Improve readability of function invocations
    helloFunctions("functions", "functions")
    • How do we know which value is correct?
    @n8ebel #droidconbos 22

    View Slide

  23. Named Arguments
    Much easier to understand with named arguments
    helloFunctions(exclamation = "yay!", excitingThing = "functions")
    @n8ebel #droidconbos 23

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  32. Variable Number of Arguments
    This won't compile
    helloFunctions("Droidcon Boston", exlamation = "yay!", "Kotlin", "Android")
    // error: "no matching function"
    @n8ebel #droidconbos 32

    View Slide

  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

    View Slide

  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

    View Slide

  35. Return Types
    What is the return type?
    fun helloFunctions(exclamation:String, excitingThing:String="functions") {
    println(exclamation + ", " + excitingThing)
    }
    @n8ebel #droidconbos 35

    View Slide

  36. If a function does not
    return any useful value, its
    return type is Unit
    @n8ebel #droidconbos 36

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  40. Generic Functions
    Like classes, functions may have generic type parameters
    public inline fun Iterable.filter(predicate: (T) -> Boolean): List {
    return filterTo(ArrayList(), predicate)
    }
    public fun Iterable.toHashSet(): HashSet {
    return toCollection(HashSet(mapCapacity(collectionSizeOrDefault(12))))
    }
    listOf(2,4,6,8).filter{ ... }
    setOf(2,4,6).toHashSet()
    @n8ebel #droidconbos 40

    View Slide

  41. concise
    convenient
    flexible
    @n8ebel #droidconbos 41

    View Slide

  42. What Next?
    @n8ebel #droidconbos 42

    View Slide

  43. Let's explore some
    variations on how you can
    create and use functions
    @n8ebel #droidconbos 43

    View Slide

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

    View Slide

  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

    View Slide

  46. Top-Level Function Patterns
    • Replace stateless classes filled with static methods
    • Swap your "Util" or "Helper" classes with functions
    @n8ebel #droidconbos 46

    View Slide

  47. Top-Level Function
    Considerations
    • Not truly removing classes
    • Generated as a public static method on a class using a
    special convention
    • Kt.java
    @n8ebel #droidconbos 47

    View Slide

  48. Top-Level Function
    Considerations
    Inside Logging.kt
    package logging
    fun log(error:Throwable) {...}
    @n8ebel #droidconbos 48

    View Slide

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

    View Slide

  50. Top-Level Function
    Considerations
    Generated Code
    public class LoggingKt {
    public static void log(Throwable error) {...}
    }
    @n8ebel #droidconbos 50

    View Slide

  51. Top-Level Function
    Considerations
    Call from Java
    LoggingKt.log(new Throwable("oops"))
    @n8ebel #droidconbos 51

    View Slide

  52. Top-Level Function
    Considerations
    Can override the generated class/file name
    • Add @file:JvmName() to function's
    file
    • Must be before the declared package
    @n8ebel #droidconbos 52

    View Slide

  53. Top-Level Function
    Considerations
    Inside Logging.kt
    @file:JvmName("LoggingFunctions")
    package logging
    fun log(error:Throwable) {...}
    @n8ebel #droidconbos 53

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  57. Member Functions
    class Speaker() {
    fun giveTalk() { ... }
    }
    // create instance of class Speaker and call giveTalk()
    Speaker().giveTalk()
    @n8ebel #droidconbos 57

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  61. Local Functions
    Why would you want this?
    • Clean code
    • Avoids code duplication
    • Avoid deep chains of function calls
    @n8ebel #droidconbos 61

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  69. Companion Object Function
    Considerations
    Java usage is ugly
    // from Java
    Course.Companion.createCourse("somekey")
    @n8ebel #droidconbos 69

    View Slide

  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

    View Slide

  71. different scopes for
    different use cases
    @n8ebel #droidconbos 71

    View Slide

  72. Variations
    @n8ebel #droidconbos 72

    View Slide

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

    View Slide

  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

    View Slide

  75. infix
    • Must be a member function or extension function
    • Must take a single, non-varargs, parameter with no default
    value
    @n8ebel #droidconbos 75

    View Slide

  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

    View Slide

  77. infix
    • Provides a very clean, human-readable syntax
    • Core building block of custom DSLs
    @n8ebel #droidconbos 77

    View Slide

  78. infix
    "hello" should haveSubstring("ell")
    "hello" shouldNot haveSubstring("olleh")
    https://github.com/kotlintest/kotlintest
    @n8ebel #droidconbos 78

    View Slide

  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

    View Slide

  80. Why Extension Functions?
    • Clean-up or extend classes & apis you don't control
    • Remove helper classes & simplify top-level functions
    @n8ebel #droidconbos 80

    View Slide

  81. Extension Functions
    // add a new function to the View class
    fun View.isVisible() = visibility == View.VISIBLE
    yourView.isVisible()
    @n8ebel #droidconbos 81

    View Slide

  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

    View Slide

  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

    View Slide

  84. Extension Function
    Considerations
    • How are these generated under the hood?
    • How are these called from Java?
    @n8ebel #droidconbos 84

    View Slide

  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.
    @n8ebel #droidconbos 85

    View Slide

  86. Extension Function
    Considerations
    // ContextExtensions.kt
    fun Context.showToast(...) { ... }
    // when called from Java
    ContextExtensionsKt.showToast(context, "Toast!");
    @n8ebel #droidconbos 86

    View Slide

  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

    View Slide

  88. Higher-Order Functions
    • Enable interesting patterns & conventions
    • Support functional programming
    • Can cleanup setup/teardown patterns such as shared prefs
    @n8ebel #droidconbos 88

    View Slide

  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

    View Slide

  90. Higher-Order Functions
    val predicate = { number:Int -> number > 5 }
    listOf(2,4,6,8).filter(predicate)
    @n8ebel #droidconbos 90

    View Slide

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

    View Slide

  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

    View Slide

  93. Higher-Order Functions
    public inline fun synchronized(lock: Any, block: () -> R): R {
    monitorEnter(lock)
    try {
    return block()
    }
    finally {
    monitorExit(lock)
    }
    }
    // call from Kotlin
    synchronized(database) {
    database.prePopulate()
    }
    @n8ebel #droidconbos 93

    View Slide

  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

    View Slide

  95. inline
    • Helps solve higher-order function performance hits
    • Body of the inlined function is substituted for invocations of
    the function
    @n8ebel #droidconbos 95

    View Slide

  96. inline
    inline fun synchronized(lock: Lock, action: () -> T): T {
    lock.lock()
    try {
    return action()
    }
    finally {
    lock.unlock()
    }
    }
    // call from Kotlin
    synchronized(Lock()) {...}
    @n8ebel #droidconbos 96

    View Slide

  97. inline
    // sample usage
    fun inlineExample(l:Lock) {
    println("before")
    synchronized(l) {
    println("action")
    }
    println("after")
    }
    @n8ebel #droidconbos 97

    View Slide

  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

    View Slide

  99. Android Reimagined
    These features enabled us to rethink how we build our apps
    @n8ebel #droidconbos 99

    View Slide

  100. Fewer Helper Classes
    • ContextHelper, ViewUtils
    • Replace with
    • top-level functions
    • extension functions
    @n8ebel #droidconbos 100

    View Slide

  101. Less Boilerplate
    fun doTheThingSafely(theThing:() -> Unit) {
    try {
    theThing()
    } catch(error:Throwable) {
    // handle error
    }
    }
    @n8ebel #droidconbos 101

    View Slide

  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

    View Slide

  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

    View Slide

  104. Cleaner Syntax
    fun log(msg:String) {...}
    inline fun runOnBackgroundThread(action:() -> Unit) { ... }
    • More fluent syntax
    • Simplify test mocking
    • Avoids extra classes
    @n8ebel #droidconbos 104

    View Slide

  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

    View Slide

  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

    View Slide

  107. Kotlin functions provide
    flexibility & freedom in
    how you build your apps
    @n8ebel #droidconbos 107

    View Slide

  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

    View Slide

  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

    View Slide

  110. Thanks For Coming
    @n8ebel #droidconbos 110

    View Slide

  111. Let's Continue the Discussion
    with("n8ebel").apply {
    Twitter
    .com
    Medium
    Instagram
    Facebook
    GitHub
    }
    @n8ebel #droidconbos 111

    View Slide