Scope functions apply/with/run/also/let: Where do their names come from?
Asked Answered
R

4

17

There are quite a few blog posts (like this) on usages of the standard library functions apply/with/run/also/let available that make it a bit easier to distingish when to actually use which of those pretty functions.

For a few weeks now, the official docs even provide guidelines on that topic finally: https://kotlinlang.org/docs/reference/coding-conventions.html#using-scope-functions-applywithrunalsolet

Nevertheless, I think it is pretty hard to memorize the function's individual use cases by the function names. I mean, for me they seem to be interchangeable, why isn't let called run for instance?

Any suggestions? I think the names aren't very expressive which makes it hard to see the differences at first.

Rhizotomy answered 12/1, 2018 at 1:42 Comment(4)
These are very situational functions. You'll memorize them by using them when need it. For ex: I wanted to know how to continue after ?. using nullables and I found let. Learned it on the spot. Same with with or 'also'. Once you know they exist, you'll get into the cases when you know there is something but you don't remember which is. Several trips to the docs will ensure you'll know them in the future. At least this is how I'm doing itIlluminance
This isn't really a problem for me either. I always know one of them that can be used in particular situations. But the thing is, the function names do not really tell what they really do.Rhizotomy
Well, you know the hardest thing in programming...Illuminance
A while ago I made a fuss when you answered a question that had been asked before and was later closed as a duplicate. I used to be overly passionate about keeping this site clean and everything that comes with it and it resulted in me acting not-so-nice to you. I have since learned to let go of this excessive passion and I apologize for acting the way I did. You do great work on here and your contributions are much appreciated, keep it up.Hereupon
L
27

Here's an unofficial overview of how the names seem to have come to be.

let

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

a "let" expression associates a function definition with a restricted scope

In FP languages like Haskell you can use let to bind values to variables in a restricted scope like so

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

The equivalent (albeit overly complicated) code in Kotlin would be

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

The typical usage of let is to bind the result of some computation to a scope without "polluting" the outer scope.

creater.createObject().let {
    if (it.isCorrect && it.shouldBeLogged) {
        logger.log(it)
    }
}

// `it` is out of scope here

with

The with function is inspired by the with language construct from languages like Delphi or Visual Basic (and probably many others) where

The with keyword is a convenience provided by Delphi for referencing 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;

The equivalent Kotlin would be

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

apply

apply was added to the stdlib relatively late in the milestone phase (M13). You can see this question from 2015 where a user asks for exactly such a function and even suggests the later to-be-used name "apply".

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

apply is similar to with in that it can be used to initialize objects outside of the constructor. That's why, in my opinion, apply might as well be named with. However as with was added to the stdlib first, the Kotlin devs decided against breaking existing code and added it under a different name.

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

also

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

Among the proposed names were "applyIt", "applyLet", "on", "tap", "touch", "peek", "make". But "also" won as it doesn't collide with any keywords or other stdlib functions and its usages (more or less) read like English sentences.

Example

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

reads a bit like

Creater, create the object and also initialize it!

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

run

Finally, the run function actually has two signatures. The first one fun <R> run(block: () -> R): R simply takes a lambda and runs it. It is mostly used for assigning 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 fun <T, R> T.run(block: T.() -> R): R is an extension function which takes an extension lambda as parameter and seems to also be named "run" for symmetry reasons. It also "runs" a lambda but in the context of an extension receiver

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

I'm not aware of any historical reasons for the naming.

Linkwork answered 12/1, 2018 at 8:58 Comment(3)
Thank you! For me, with does makes sense as it is, because the receiver is passed as the first argument, which we do something with, thus I wouldn’t like apply to be named withRhizotomy
For more clarification in kotlin android: medium.com/@khadijahameed415/…Widow
with returns the block function's evaluation result, therefore with is closer to run than applyDakotadal
T
10

I strongly recommend to read this blog in order to understand all of these scope functions.

Some keys of these blog:

  1. LARA functions

enter image description here

Following the first letter of each, you get the acronym “LARA”.

  1. Code Comparison

enter image description here

  1. Common use cases

  2. With

with() is functionally the same as the extension function version of run(), so it's well-suited to the use case of Initialize and execute. More information.

Tiepolo answered 12/1, 2018 at 11:21 Comment(0)
J
9

Adding to the @kirillRakhman answer:

A major part in the naming process was (still is) fluent reading experience in major 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 it doesn't really work with let well. After all, it was taken from "those scary FP languages". But I often think of it as a sort of Let's do this! construct. Like below you could read the code as let's print it!:

account.map { it.owner }.sumBy {age}.let { print(it) }
Jubilee answered 12/1, 2018 at 10:7 Comment(2)
I do recall a discussion we had about let on slack recently ;-)Rhizotomy
@Rhizotomy the more I discuss let, the less I am satisfied with its naming :PJubilee
D
0

Summary of Scope functions:

let: Used to check nulls, also better than simple null check in multi-threading case

also: same as 'let' but it doesn't return the last line as 'let', instead 'also' will return the object it was called on and 'not the last line!'

apply: helpful function to modify objects, if you want to change in properties of the objects, and it uses 'this' instead of 'it' as we work inside the class of the object

run: equivalent to 'apply', but it won't return the object it was called, instead it will return the last line

with: same as 'run' but a different signature.

Discommon answered 27/9, 2021 at 8:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.