The scope functions apply / with / run / also / let: Where do the names come from?

There are several blog posts (like this ) about using the standard apply / with / run / also / let library functions, which makes it a little easier to actually use which of these nice features.

For several weeks, official docs even contain recommendations on this topic: https://kotlinlang.org/docs/reference/coding-conventions.html#using-scope-functions-applywithrunalsolet

However, I think it’s very difficult to remember functions individual use cases by function names . I mean, for me they seem to be interchangeable, why not let run be called for example?

Any suggestions? I think that the names are not very expressive, which makes it difficult to perceive the differences at first.

+7
lambda kotlin scoping higher-order-functions
source share
3 answers

Here's an unofficial overview of how names seem to have become.

let

let inspired by the world of functional programming. According to Wikipedia

the expression "let" associates a function definition with a limited scope

In FP languages ​​like Haskell, you can use let to bind values ​​to variables in a restricted area, like

 aaa = let y = 1+2 z = 4+6 in y+z 

Equivalent (albeit too complicated) code in Kotlin would be

 fun aaa() = (1+2).let { y -> (4+6).let { z -> y + z } } 

A typical use of let is to bind the result of some calculations to an area without “contaminating” the outer area.

 creater.createObject().let { if (it.isCorrect && it.shouldBeLogged) { logger.log(it) } } // `it` is out of scope here 

from

The with function is inspired by the construction of the with language in languages ​​such as Delphi or Visual Basic (and possibly many others), where

A keyword with a keyword is the convenience provided by Delphi to reference elements of a complex variable, such as a record or object.

 myObject.colour := clRed; myObject.size := 23.5; myObject.name := 'Fred'; 

can be rewritten:

 with myObject do begin colour := clRed; size := 23.5; name := 'Fred'; end; 

Equivalent Kotlin would be

 with(myObject) { color = clRed size = 23.5 name = "Fred" } 

To apply

apply was added to stdlib at a relatively late stage stage (M13). You can see this question from 2015, when the user requests just such a function and even offers to use the later used name "apply".

In the problems https://youtrack.jetbrains.com/issue/KT-6903 and https://youtrack.jetbrains.com/issue/KT-6094 you can see naming discussions. Alternatives such as build and init were proposed, but the name apply , proposed by Daniil Vodopyan, ultimately won.

apply is similar to with in that it can be used to initialize objects outside the constructor. That is why, in my opinion, apply can also be called with . However, since with was first added to stdlib, Kotlin developers decided not to violate the existing code and added it under a different name.

Oddly enough, the Xtend language provides the so-called with the => operator , which basically does the same thing as apply .

and

also was added to stdlib even later apply , namely in version 1.1. Again, https://youtrack.jetbrains.com/issue/KT-6903 contains a discussion. The function is basically similar to apply , except that instead of the lambda T.() -> Unit extension, a regular lambda (T) -> Unit is required.

Among the suggested names were "applyIt", "applyLet", "on", "tap", "touch", "peek", "make". But he also won because he does not encounter any keywords or other functions of stdlib, and his customs (more or less) are considered English sentences.

Example

 val object = creater.createObject().also { it.initiliaze() } 

reads a bit like

Creater, create an object and also initialize it!

Other stdlib features whose habits read a bit like English sentences include takeIf and takeUnless , which were also added in version 1.1.

run

Finally, the run function actually has two signatures. First fun <R> run(block: () -> R): R just accepts lambda and works . It is mainly used to assign the result of a lambda expression to a top-level property.

 val logger = run { val name = System.property("logger_name") Logger.create(name) } 

The second signature is fun <T, R> T.run(block: T.() -> R): R is an extension function that takes the lambda extension as a parameter and is apparently also called "run" for symmetry. It also “launches” lambda, but in the context of an expansion receiver

 val result = myObject.run { intitialize() computeResult() } 

I do not know any historical reasons for naming.

+15
source share

Adding to @kirillRakhman's answer:

The most important part of the naming process was (as before) free reading experience in basic use cases.

with :

 with(database) { open() send() close() } 

apply :

 val v = View().apply { width = 3.0 height = 4.0 register(this) } 

also :

 db.users() .filter { it.age > 18 } .map { account } .also { log(it) } 

IMHO does not actually work with let . In the end, it was taken from "these scary FP languages." But I often think of it as a Let do this! construct Let do this! . As below, you can read the code as let print it! :

 account.map { it.owner }.sumBy {age}.let { print(it) } 
+5
source share

I highly recommend reading this blog to understand all these features of the area.

Some keys to this blog post:

  • LARA Functions

enter image description here

Following the first letter of each, you get the abbreviation "LARA".

  1. Code Comparison

enter image description here

  1. Common use cases

  2. FROM

with() functionally matches the version of the run() extension function, so it is well suited to use Initialize and Run . More info .

+5
source share

All Articles