Javascript required
Skip to content Skip to sidebar Skip to footer

How to Write a Function When Run Again Adds to Itself

Coping with Kotlin's Scope Functions

Functions in Kotlin are very important and it's much fun() to employ them. 1 special drove of relevant functions can be described as "scope functions" and they are part of the Kotlin standard library: permit, run, besides, use and with.
You lot probably already heard most them and it'due south also likely that y'all even used some of them however. Most people tend to have bug distinguishing all those functions, which is non very remarkable in view of the fact that their names may be a bit disruptive. This mail service intends to demonstrate the differences between the available scope functions and also wants to discuss relevant use cases. Finally, an example volition bear witness how to use scope functions and how they assist to structure Kotlin code in a more than idiomatic way.

Disclaimer: The topic of scope functions is nether consideration in various StackOverflow posts very frequently, which I will occasionally refer to throughout this commodity.

The Importance of Functions

In Kotlin, functions are as important as integers or strings. Functions can exist on the same level equally classes, may be assigned to variables and can besides be passed to/returned from other functions. Kotlin makes functions "kickoff-class citizens" of the language, which Wikipedia describes as follows:

A starting time-grade citizen [...] is an entity which supports all the operations generally available to other entities. These operations typically include being passed every bit an argument, returned from a function, modified, and assigned to a variable.

As already said, functions are as powerful and significant every bit any other type, e.g. Int. In addition to that, functions may announced equally "higher-order functions", which in turn is described every bit the following on Wikipedia:

In mathematics and computer science, a higher-order function (too functional, functional form or functor) is a function that does at to the lowest degree one of the post-obit:
- takes ane or more functions as arguments (i.e., procedural parameters),
- returns a role as its issue.

The boldly printed bullet indicate is the more important ane for the present commodity since scope functions also deed as higher-society functions that accept other functions every bit their argument. Before we swoop into this farther, let's observe a simple example of college-lodge functions.

Higher-Social club Role in Activeness

A simple college-order role that'south commonly known in Kotlin is called repeat and it's defined in the standard library:

                inline fun repeat(times: Int, activity: (Int) -> Unit)                              

Equally you can see, repeat takes two arguments: An ordinary integer times and as well another function of type (Int) -> Unit. According to the previously depicted definition, repeat is a higher-gild function since information technology "takes one or more functions every bit arguments". In its implementation, the function simply invokes activity as frequently as times indicates. Allow'due south come across how echo can be chosen from a client'due south point of view:

                repeat(3) { rep ->     println("current repetition: $rep") }                              

In Kotlin, lambdas can be lifted out of the parentheses of a function call if they human action as the concluding argument to the function.

Note that if a function takes another function as the last parameter, the lambda expression statement can be passed exterior the parenthesized argument list.

The official documentation is very clear about all lambda features and I highly recommend to study it.

In the shown snippet, a regular lambda, which only prints the electric current repetition to the console, is passed to repeat. That's how higher-club function calls wait like.

Function Literal with Receiver

Kotlin promotes yet another very of import concept that makes functions even more than powerful. If you've always seen internal domain specific languages (DSL) in action, you might take wondered how they are implemented. The most relevant concept to understand is called function literal with receiver (also lambda with receiver). Since this feature is likewise vital for scope functions, it will exist discussed next.

Function literals with receiver are ofttimes used in combination with college-guild functions. As shown earlier, functions can be made parameters of other functions, which happens by defining parameters with the function blazon syntax (In) -> Out. Now imagine that these function types can even be boosted by adding a receiver: Receiver.(In) -> Out. Such types are called part literal with receiver and are all-time understood if visualized as "temporary extension functions". Take the following example:

                inline fun createString(block: StringBuilder.() -> Unit): String {     val sb = StringBuilder()     sb.block()     render sb.toString() }                              

The part createString can be called a higher-guild role every bit it takes another part block as its argument. This argument is defined as a function literal with receiver type. Now, let'south think of it equally an extension part defined for StringBuilder that will be passed to the createString role. Clients will manus on arbitrary functions with the signature () -> Unit, which will be callable on instances of StringBuilder. That's also shown in the implementation: An instance of StringBuilder is existence created and cake gets invoked on it. Somewhen, the method transforms StringBuilder to an ordinaryCord` and to the caller.

What does that hateful for the client of such a method? How tin you create and pass office literals with receiver to other functions? Since the receiver is defined as StringBuilder, a client volition be able to laissez passer lambdas to createString that brand use of that receiver. The receiver is exposed as this inside the lambda, which means that clients can access visible members (properties, functions etc.) without additional qualifiers:

                                  
val southward = createString { //here we're in the context of a StringBuilder append(4) suspend("hello") }

The example shows that append, a function defined for the receiver StringBuilder, is being invoked without whatsoever qualifiers (e.g. information technology). The aforementioned is possible in the definition of extension functions, which is why I used it as an analogy earlier. The client defines a temporary extension office which gets invoked on the respective receiver within createString afterward. For another description of the concept, delight consult the associated documentation. I also tried to respond a related StackOverflow question a while ago.

Scope Functions

Scope functions brand use of the concepts described above. They are defined as higher-society functions, i.due east. they take some other function as their statement. These arguments may even appear as function literals with receiver in certain cases. Scope functions accept an arbitrary object, the context object, and bring it to another scope. In that telescopic, the context object is either accessible equally it (or custom name) or this, depending on the type of function. In the following, the functions let, run, also, apply and with will be introduced and explained.

[let]

Documentation: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/let.html

                public inline fun <T, R> T.let(block: (T) -> R): R                              

I of the most famous scope functions is certainly let. It'southward inspired by functional programming languages like Haskell and is used quite often in the Kotlin language, too. Let's inspect its signature:

  • Defined equally an extension on T, the receiver/context object
  • Generic type R defines the function's return value
  • Result R of block will be the result of let itself, i.due east. information technology can be an arbitrary value
  • block statement with regular function type (T) -> R
  • Receiver T is passed as argument to block

Utilize Cases

a. Idiomatic replacement for if (object != goose egg) blocks

As you tin read in the Kotlin Idioms section, allow is supposed to exist used to execute blocks if a certain object is non null.

                val len = text?.let {     println("get length of $information technology")     it.length } ?: 0                              

The nullable text variable is brought into a new scope past let if it isn't null. Its value then gets mapped to its length. Otherwise, the zippo value is mapped to a default length 0 with the assistance of the Elvis operator. Every bit you can come across, the context object text gets exposed every bit it within let, which is the default implicit name for single parameters of a lambda.

b. Map nullable value if non cipher

The allow role is also often used for transformations, especially in combination with nullable types over again, which is besides defined as an idiom.

                val mapped = value?.allow { transform(information technology) } ?: defaultValue                              

c. Confine scope of variable/ciphering

If a sure variable or ciphering is supposed to be bachelor only in a bars scope and should not pollute the outer scope, let again tin can be helpful:

                val transform = "stringConfinedToLetScope".allow {     println("variable tin be accessed in permit: $it")     "${it.length}$it" } //cannot access original string from here }                              

The shown string "stringConfinedToLetScope" is made the context object of let, which uses the value for some simple transformation that is returned as the upshot of let. The outer telescopic only uses the transformed value and does not admission the temporarily needed string. In that location's no variable polluting the outer telescopic due to confining it to the relevant scope.

[run]

Documentation: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run.html

                inline fun <T, R> T.run(block: T.() -> R): R                              

Equally an culling to let, the run function makes employ of a part literal with receiver as used for the block parameter. Let's inspect its signature:

  • Defined as an extension on T, the receiver/context object
  • Generic type R defines the function's return value
  • Result R of cake will be the event of run itself, i.e. information technology can be an arbitrary value
  • block argument divers as function literal with receiver T.() -> R

The run function is similar permit except how cake is defined.

Apply Cases

run can basically serve the same use cases as let, whereas the receiver T is exposed every bit this within the lambda argument:

a. Idiomatic replacement for if (object != null) blocks

                val len = text?.run {     println("become length of $this")     length //this                  tin can be omitted } ?: 0                              

b. Transformation

It'southward also skilful to use run for transformations. The following shows an example that is even more than readable than with permit since it accesses the context object'due south functions without qualifiers:

                import java.util.Calendar  val appointment: Int = Agenda.getInstance().run {     set(Calendar.Year, 2030)     go(Calendar.DAY_OF_YEAR) //return value of run }                              

[also]

Documentation: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/as well.html

                inline fun  T.as well(block: (T) -> Unit): T                              

The besides role is the scope function that got lastly added to the Kotlin language, which happened in version 1.1. Allow's audit its signature:

  • Defined as an extension on T, the receiver/context object
  • Returns the receiver object T
  • cake statement with regular function blazon (T) -> Unit of measurement
  • Receiver T is passed as argument to block

also looks similar let, except that it returns the receiver T as its consequence.

Use Cases

a. Receiver not used inside the block

It might be desired to practice some tasks related to the context object merely without actually using it inside the lambda statement. An example can be logging. Equally described in the official Kotlin coding conventions, using too is the recommended way to solve scenarios like the one shown side by side:

                val num = 1234.also {     log.debug("the function did its job!") }                              

In this case, the lawmaking almost reads like a normal sentence: Assign something to the variable and also log to the console.

b. Initializing an object

Another very common scenario that tin be solved with also is the initialization of objects. As opposed to the two previously introduced telescopic functions, let and run, besides returns the receiver object after the block execution. This fact tin can exist very handy:

                val bar: Bar = Bar().also {     information technology.foo = "another value" }                              

As shown, a Bar instance is created and also is utilized in order to directly initialize 1 of the instance's properties. Since also returns the receiver object itself, the expression can directly be assigned to a variable of type Bar.

c. Assignment of calculated values to fields

The fact that also returns the receiver object after its execution can also be useful to assign calculated values to fields, as shown here:

                fun getThatBaz() = calculateBaz().as well { baz = it }                              

A value is being calculated, which is assigned to a field with the aid of also. Since also returns that calculated value, it tin can fifty-fifty be made the direct inline result of the surrounding part.

[use]

Documentation: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/use.html

                inline fun  T.apply(block: T.() -> Unit): T                              

The apply function is another telescopic function that was added because the community asked for it. Its main utilise case is the initialization of objects, like to what too does. The deviation volition be shown next. Let's inspect its signature first:

  • Defined as an extension on T, the receiver/context object
  • Returns the receiver object T
  • cake argument defined as part literal with receiver T.() -> R

The relation between apply and likewise is the same as betwixt let and run: Regular lambda vs. Function literal with receiver parameter:

Relation (apply,also) == Relation (run,let)

Employ Cases

a. Initializing an object

The ultimate use case for utilize is object initialization. The customs actually asked for this part in relatively late phase of the linguistic communication. You tin can find the respective feature request hither.

                val bar: Bar = Bar().apply {     foo1 = Colour.RED     foo2 = "Foo" }                              

Although also was already shown equally a tool for solving these scenarios, it's obvious that apply has a large advantage: There's no need to use "it" as a qualifier since the context object, the Bar case in this example, is exposed as this. The divergence got answered in this StackOverflow mail service.

b. Architect-style usage of methods that return Unit

Equally described in the Kotlin Idioms department, apply can be used for wrapping methods that would normally result in Unit responses.

                data grade FooBar(var a: Int = 0, var b: Cord? = cypher) {     fun first(aArg: Int): FooBar = use { a = aArg }     fun 2nd(bArg: String): FooBar = utilize { b = bArg } }  fun main(args: Array<String>) {     val bar = FooBar().offset(10).second("foobarValue")     println(bar) }                              

In the example, apply is used to wrap simple property assignments that would usually simply issue in Unit. Since the course wants to expose a builder-style API to the customer, this approach is very useful as the setter-like methods return the surrounding object itself.

[with]

Documentation: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/with.html

                inline fun <T, R> with(receiver: T, block: T.() -> R): R                              

The with part is the final scope part that will exist discussed hither. It's a very mutual feature in many fifty-fifty older languages like Visual Basic and Delphi.
Information technology varies from the other four functions in that it is non defined every bit an extension function. Allow's inspect its signature:

  • Defined as an contained function that takes a receiver/context object T as its showtime argument
  • Result R of block volition be the result of with itself, i.e. information technology can be an capricious value
  • block argument defined as part literal with receiver T.() -> R

This office aligns with let and run in regards to its return value R. Information technology'southward oftentimes said to exist similar to employ; the difference got described here. Another simple clarification of with can be establish here (both StackOverflow).

Apply Cases

a. Working with an object in a confined scope

Likewise defined equally an idiom, with is supposed to be used when an object is but needed in a certain confined telescopic.

                val south: Cord = with(StringBuilder("init")) {     append("some").append("thing")     println("current value: $this")     toString() }                              

The StringBuilder passed to with is only acting every bit an intermediate instance that helps in creating the more relevant Cord that gets created in with. It'due south obvious that with is utilized for wrapping the calls to StringBuilder without exposing the example itself to the outer telescopic.

b. Using member extensions of a grade

Extension functions are usually defined on bundle level so that they can be imported and accessed from anywhere else with ease. It'southward also possible to ascertain these on form or object level, which is and then called a "member extension function". These kinds of extension functions can easily be used inside that class only not from exterior. In order to make them accessible from anywhere outside the enclosing class, that course has to be brought "into scope". The with office is very useful here:

                object Foo {     fun ClosedRange<Int>.random() =         Random().nextInt(endInclusive - showtime) + first }  // random() can just be used in context of                  Foo                  with(Foo) {     val rnd = (0..10).random()     println(rnd) }                              

The shown object Foo defines a sweet member extension part random(), which can be used only in the scope of that object. With the help of with, this can easily be achieved. Annotation that this strategy is especially recommendable if particular extension functions are to be grouped meaningfully.

Comparison and Overview

After the five different scope functions have been discussed, it's necessary to see them all next to each other:

                //return receiver T fun  T.also(block: (T) -> Unit of measurement): T //T exposed as it fun  T.use(block: T.() -> Unit): T //T exposed every bit this  //return arbitrary value R fun <T, R> T.let(block: (T) -> R): R //T exposed as it fun <T, R> T.run(block: T.() -> R): R //T exposed every bit this  //return arbitrary value R, not an extension function  fun <T, R> with(receiver: T, cake: T.() -> R): R //T exposed equally this                              

The telescopic functions also and utilise both render the receiver object afterward their execution. In apply, the cake parameter is defined as a function literal with receiver and T gets exposed as this, whereas in too, it'southward a regular function type and T gets exposed as it.

The scope functions let and run on the other hand both return an arbitrary effect R, i.e. the issue of the block itself. Again, run works with a function literal with receiver, whereas let uses the simple function blazon.

Last only not to the lowest degree, with is kind of a misfit amongst the scope functions since information technology'southward not defined every bit an extension on T. It defines two parameters, one of which represents the receiver object of this scope function. Aforementioned as employ and run, with works with function literal with receiver.

returns receiver object returns arbitrary event
exposed as it besides let
exposed as this utilize run & with i

1Non an extension.

IDE Back up

As of version i.2.thirty, the IntelliJ Thought Kotlin plugin offers intentions that can convert between let and run and likewise between also and utilize calls. Read more about it here.

Example: Requesting a REST API

In this section, I'm going to show an example that applies the previously discussed scope functions on a pretty bones utilise case: Calling an HTTP REST endpoint. The goal is to provide functionality for requesting information about contributors of the jetbrains/kotlin GitHub project. Therefore we ascertain the appropriate GitHub endpoint and a simplified representation of a Contributor that is annotated for Jackson:

                const val ENDPOINT = "https://api.github.com/repos/jetbrains/kotlin/contributors"  @JsonIgnoreProperties(ignoreUnknown = true) data course Contributor(     val login: String,      val contributions: Int )                              

The following shows the code that provides the desired functionality in its initial form:

                object GitHubApiCaller {     private val client = OkHttpClient()     private var cachedLeadResults =         mutableMapOf<Cord, Correspondent>()     private val mapper = jacksonObjectMapper()      @Synchronized     fun getKotlinContributor(proper noun: String): Contributor {         val cachedLeadResult = cachedLeadResults[name]         if (cachedLeadResult != null) {             LOG.debug("return cached: $cachedLeadResult")             return cachedLeadResult         }         val asking = Request.Builder().url(ENDPOINT).build()          val response = customer.newCall(request).execute()          val responseAsString = response.use {             val responseBytes = it.trunk()?.source()?.readByteArray()             if (responseBytes != null) {                 Cord(responseBytes)             } else throw IllegalStateException("No response from server!")         }          LOG.debug("response from git api: $responseAsString\north")          val contributors =              mapper.readValue(responseAsString)          val friction match = contributors.first { it.login == name }         this.cachedLeadResults[name] = match         LOG.debug("establish kotlin contributor: $match")         return match     } }                              

The depicted snippet shows a singleton object GitHubApiCaller with an OkHttpClient (OkHttp), a Jackson mapper and a simple Map that'south used for caching results. The code of getKotlinContributor can be decomposed into the following sub-tasks:

  • When the result is already cached, render it immediately and skip the rest
  • Create a request object using the ENDPOINT
  • Get the response by executing the asking on the client
  • Extract the JSON data from the response object (Error handling omitted)
  • De-serialize the JSON to an Array
  • Filter for the contributor that is searched for
  • Cache the consequence and return it to the client

In my stance, this code is very comprehensive and everybody is able to make use of information technology. Withal, it tin exist taken as a good footing for a little refactoring.

Reviewing the Code

Allow's at present effort to find some appropriate employ cases for telescopic functions in the previously shown function.

Refactoring No. 1

The start thing that nosotros tin can amend is the if block in the very beginning:

                val cachedLeadResult = cachedLeadResults[proper name] if (cachedLeadResult != null) {     println("return cached: $cachedLeadResult")     return cachedLeadResult }                              

As shown before, the let function is normally used for resolving these kinds of if blocks. Applied to the concrete example, nosotros get the following:

                render cachedLeadResults[name]?.let {     LOG.debug("return cached: $it")     information technology }                              

The problem here is that permit is defined with a generic return type R so that the it needs to be written at the cease in order to brand information technology the render value of the expression. Another obvious insufficiency is the missing else statement. The first problem tin be addressed pretty easily. Nosotros just demand to use a scope function that returns its receiver, i.e. the cached upshot, directly from the block. Additionally, information technology should notwithstanding betrayal the receiver as information technology, which makes likewise the best suitable candidate:

                @Synchronized fun getKotlinContributor(proper noun: Cord): Contributor {     return cachedLeadResults[name]?.also {         LOG.debug("return cached: $it")     } ?: requestContributor(proper noun) }                              

The Elvis operator, shown before, is very often used for handling the else case, i.e. when the receiver is null. In order to brand the code more readable, a individual function requestContributor now handles the cache miss.

That'south it, the if block was replaced with an easy also invocation. A more than idiomatic solution.

Refactoring No. 2

The next portion that is worth reconsidering contains an unnecessary local variable that represents the request object:

                val request = Request.Builder().url(ENDPOINT).build() val response = customer.newCall(asking).execute()                              

It'south literally merely used for getting a response object from the client and could therefore simply be inlined. Alternatively, the shown actions can be idea of as a basic transformation, which we learned to limited with the permit function:

                val response =      Request.Builder().url(ENDPOINT).build().allow { customer.newCall(it).execute() }                              

The request has been made the context object of permit and directly gets executed in a straightforward transformation. It makes the local request variable obsolete without affecting readability negatively.

Refactoring No. 3

The post-obit snippet points at another if (obj != aught) block, which in this case tin actually be solved with permit:

                // Before Refactoring val responseAsString = response.employ {     val responseBytes = it.body()?.source()?.readByteArray()     if (responseBytes != null) {         String(responseBytes)     } else throw IllegalStateException("No response from server!") }  // With Scope Function  val responseAsString = response.utilise {     information technology.body()?.source()?.readByteArray()?.let { String(it) }         ?: throw IllegalStateException("No response from server!") }                              

Once more, the Elvis operator handles the zip scenario very nicely.

Refactoring No. 4

Moving on to the next ii statements of the code, we observe that the responseAsString variable is existence logged and finally used for the Jackson de-serialization. Let'south group them together:

                // Before Refactoring LOG.debug("response from git api: $responseAsString\north") val contributors =      mapper.readValue(responseAsString)  // With Telescopic Function val contributors = responseAsString.let {     LOG.debug("response from git api: $it\n")      mapper.readValue(it) }                              

Refactoring No. v

Subsequently the first few refactorings, the state of affairs looks as follows: Nosotros have a response, a responseAsString and a contributors variable and still demand to filter the Correspondents for the desired entry. Basically, the whole requesting and response handling is not relevant for the final step of filtering and caching. We can smoothly group these calls and confine them to their ain telescopic. Since these actions happen with the help of the OkHttpClient, it makes sense to make the client the context object of that scope:

                private fun requestContributor(proper noun: Cord): Contributor {     val contributors =         with(customer) {             val response =                 Request.Builder().url(ENDPOINT).build().permit { newCall(it).execute() }              val responseAsString = response.utilize {                 it.trunk()?.source()?.readByteArray()?.let { String(it) }                         ?: throw IllegalStateException("No response from server!")             }              responseAsString.permit {                 LOG.debug("response from git api: $it\northward")                  mapper.readValue(information technology)             }         }     //... }                              

At that place isn't whatever new code here, the previous edits have simply be wrapped in a phone call of with and are therefore not visible to the surrounding scope (function requestContributors) anymore. It made sense to utilise with in this case since it exposes client as this and the newCall invocation can therefore omit its qualifier. As described earlier, with can have an arbitrary result R. In this instance, the terminal argument within the lambda, the issue of the last allow telephone call, becomes that R.

Refactoring No. 6

Now a single variable contributors is available in the outer scope and we can apply the filtering:

                                  return contributors.start { it.login == name }.also {     cachedLeadResults[name] = it     LOG.debug("establish kotlin contributor: $it") }                              

The previous version of the above code consisted of iv independent statements that are at present grouped in a elementary also call with the filtered Correspondent every bit its receiver. That receiver is put in the enshroud and also logged for debugging purposes. Of form, since too returns its receiver directly, the whole argument can be made the return of the function.

The entire role looks like this:

                private fun requestContributor(name: String): Contributor {     val contributors =         with(client) {             val response =                 Asking.Builder().url(ENDPOINT).build().let { newCall(it).execute() }              val responseAsString = response.use {                 it.torso()?.source()?.readByteArray()?.let { Cord(it) }                         ?: throw IllegalStateException("No response from server!")             }              responseAsString.let {                 LOG.debug("response from git api: $it\n")                  mapper.readValue(information technology)             }         }      render contributors.get-go { information technology.login == name }.also {         cachedLeadResults[proper name] = it         LOG.debug("found kotlin contributor: $it")     } }                              

In my opinion, the code looks very well structured and yet readable. Yet, I don't desire to encourage the readers to apply scope functions in every state of affairs subsequently reading this article. Information technology's very important to know that this set of functions is so powerful that they could fifty-fifty be used to chain an unlimited amount of expressions and brand them a unmarried expression. You lot don't want to do that because it messes upwardly the code very rapidly. Endeavour to find a balance hither and don't employ scope functions everywhere.

Conclusion

In this commodity, I discussed the powerful set of scope functions of the Kotlin standard library. Many situations tin exist solved in a very idiomatic way with the help of these functions and it's vital to accept a rough idea of the differences between them. Try to memorize that in that location are scope functions that can render capricious values (let, run, with) and those that return the receiver itself (use, as well). Then there are functions, which expose their receiver every bit information technology (let, besides) and others, which expose their receiver equally this (run, utilize, with). The terminal example demonstrated how hands scope functions may be used for refactoring appropriate code sections according to the earlier learned concepts. You shouldn't get the impression that every unmarried opportunity should actually exist embraced; it'southward still necessary to reason most the application of scope functions. Also, you shouldn't try to use all of the shown functions at any price since most of them can exist used interchangeably sometimes. Try to detect your own favorites 🙂

You can find the shown code examples in this GitHub Repo.

Feel free to contact me and follow on Twitter. Also, check out my Getting Started With Kotlin cheat canvass here.

If you lot want to read more than about Kotlin's beautiful features I highly recommend the volume Kotlin in Activeness and my other articles to y'all.

earsmancappery51.blogspot.com

Source: https://kotlinexpertise.com/coping-with-kotlins-scope-functions/