I am learning Kotlin coming from Java, and I stumbled upon an unexpected behavior.
I noticed, that in my below code, I seem to accidentally declare a new lambda at a bad position instead of using the one I already have. How can I fix this?
I wrote these two declarations:
/**
* Dataclass used as an example.
*/
data class Meeple(var name: String, var color: String = "translucent")
/**
* Function to map from a List<T> to a new List of equal length,
* containing the ordered elements received by applying a Mapper's map
* function to every element of the input List.
*
* #param T Type of input List-elements
* #param O Type of output List-elements
* #param mapper The mapping function applied to every input element.
* #return The List of output elements received by applying the mapper on all
* input elements.
*/
fun <T, O> List<T>.map(mapper: (T) -> O?): List<O?> {
val target = ArrayList<O?>();
for (t in this) {
val mapped: O? = mapper.invoke(t)
target.add(mapped);
}
return target;
}
The data class is just a dummy example of a simple object. The List.map extension function is meant to map from the elements of the list to a new type and return a new List of that new type, almost like a Stream.map would in Java.
I then create some dummy Meeples and try to map them to their respective names:
fun main(args: Array<String>) {
val meeples = listOf(
Meeple("Jim", "#fff"),
Meeple("Cassidy"),
Meeple("David", "#f00")
)
var toFilter: String = "Cassidy"
val lambda: (Meeple) -> String? =
{ if (it.name == toFilter) null else it.name }
toFilter = "Jim"
for (name in meeples.map { lambda }) {
println(name ?: "[anonymous]") // This outputs "(Meeple) -> kotlin.String?" (x3 because of the loop)
}
}
I did this to check the behavior of the lambda, and whether it would later filter "Jim" or "Cassidy", my expectation being the later, as that was the state of toFilter at lambda initialization.
However I got an entirely different result. The invoke method, though described by IntelliJ as being (T) -> O? seems to yield the name of the lambda instead of the name of the Meeple.
It seems, that the call to meeples.map { lambda } does not bind the lambda as I expected, but creates a new lambda, that returns lambda and probably internally calls toString on that as well.
How would I actually invoke the real lambda method, instead of declaring a new one?
You already mentioned in the comments you figured out that you were passing a new lambda that returns your original lambda.
As for the toFilter value changing: The lambda function is like any other interface. As you have defined it, it captures the toFilter variable, so it will always use the current value of it when the lambda is executed. If you want to avoid capturing the variable, copy its current value into the lambda when you define the lambda. There are various ways to do this. One way is to copy it to a local variable first.
var toFilter: String = "Cassidy"
val constantToFilter = toFilter
val lambda: (Meeple) -> String? =
{ if (it.name == constantToFilter) null else it.name }
toFilter = "Jim"
Pretty much anything you can do with Stream in Java, you can do to an Iterable directly in Kotlin. The map function is already available, as mentioned in the comments.
Edit: Since you mentioned Java behavior in the comments.
Java can capture member variables, but local variables have to be marked final for the compiler to allow you to pass them to a lambda or interface. So in this sense they capture values only (unless you pass member variable). The equivalent to Java's final for a local variable in Kotlin is val.
Kotlin is more lenient than Java in this situation, and also allows you to pass a non-final local variable (var) to an interface or lambda, and it captures the variable in this case. This is what your original code is doing.
Even though you have found the issue as you mention in comments, I am adding this answer with some details to help any future readers.
So when you create lambda using
val lambda: (Meeple) -> String? = { if (it.name == toFilter) null else it.name }
This basically translates to
final Function1 lambda = (Function1)(new Function1() {
public Object invoke(Object var1) {
return this.invoke((Meeple)var1);
}
#Nullable
public final String invoke(#NotNull Meeple it) {
Intrinsics.checkNotNullParameter(it, "it");
return Intrinsics.areEqual(it.getName(), (String)toFilter.element) ? null : it.getName();
}
});
Now correct way to pass this to your map method would be as you have mentioned in comments
name in meeples.map(lambda)
but instead of (lambda) you wrote { lambda }, this is the trailing lambda convention
name in meeples.map { lambda }
// if the last parameter of a function is a function, then a lambda expression passed as the corresponding argument can be placed outside the parentheses:
// If the lambda is the only argument in that call, the parentheses can be omitted entirely
this creates a new lambda which returns the lambda we defined above, this line basically gets translated to following
HomeFragmentKt.map(meeples, (Function1)(new Function1() {
public Object invoke(Object var1) {
return this.invoke((Meeple)var1);
}
#Nullable
public final Function1 invoke(#NotNull Meeple it) {
Intrinsics.checkNotNullParameter(it, "it");
return lambda; // It simply returns the lambda you defined, and the code to filter never gets invoked
}
}))
Related
I'm trying to build a good mental model for lambdas with receivers in Kotlin, and how DSLs work. The simples ones are easy, but my mental model falls apart for the complex ones.
Part 1
Say we have a function changeVolume that looks like this:
fun changeVolume(operation: Int.() -> Int): Unit {
val volume = 10.operation()
}
The way I would describe this function out loud would be the following:
A function changeVolume takes a lambda that must be applicable to an Int (the receiver). This lambda takes no parameters and must return an Int. The lambda passed to changeVolume will be applied to the Int 10, as per the 10.lambdaPassedToFunction() expression.
I'd then invoke this function using something like the following, and all of a sudden we have the beginning of a small DSL:
changeVolume {
plus(100)
}
changeVolume {
times(2)
}
This makes a lot of sense because the lambda passed is directly applicable to any Int, and our function simply makes use of that internally (say 10.plus(100), or 10.times(2))
Part 2
But take a more complex example:
data class UserConfig(var age: Int = 0, var hasDog: Boolean = true)
val user1: UserConfig = UserConfig()
fun config(lambda: UserConfig.() -> Unit): Unit {
user1.lambda()
}
Here again we have what appears to be a simple function, which I'd be tempted to describe to a friend as "pass it a lambda that can have a UserConfig type as a receiver and it will simply apply that lambda to user1".
But note that we can pass seemingly very strange lambdas to that function, and they will work just fine:
config {
age = 42
hasDog = false
}
The call to config above works fine, and will change both the age and the hasDog properties. Yet it's not a lambda that can be applied the way the function implies it (user1.lambda(), i.e. there is no looping over the 2 lines in the lambda).
The official docs define those lambdas with receivers the following way: "The type A.(B) -> C represents functions that can be called on a receiver object of A with a parameter of B and return a value of C."
I understand that the age and the hasDog can be applied to the user1 individually, as in user1.age = 42, and also that the syntactic sugar allows us to omit the this.age and this.hasDog in the lambda declaration. But how can I reconcile the syntax and the fact that both of those will be run, sequentially nonetheless! Nothing in the function declaration of config() would lead me to believe that events on the user1 will be applied one by one.
Is that just "how it is", and sort of syntactic sugar and I should learn to read them as such (I mean I can see what it's doing, I just don't quite get it from the syntax), or is there more to it, as I imagine, and this all comes together in a beautiful way through some other magic I'm not quite seeing?
The lambda is like any other function. You aren't looping through it. You call it and it runs through its logic sequentially from the first line to a return statement (although a bare return keyword is not allowed). The last expression of the lambda is treated as a return statement. If you had not defined your parameter as receiver, but instead as a standard parameter like this:
fun config(lambda: (UserConfig) -> Unit): Unit {
user1.lambda()
}
Then the equivalent of your above code would be
config { userConfig ->
userConfig.age = 42
userConfig.hasDog = false
}
You can also pass a function written with traditional syntax to this higher order function. Lambdas are only a different syntax for it.
fun changeAgeAndRemoveDog(userConfig: UserConfig): Unit {
userConfig.age = 42
userConfig.hasDog = false
}
config(::changeAgeAndRemoveDog) // equivalent to your lambda code
or
config(
fun (userConfig: UserConfig): Unit {
userConfig.age = 42
userConfig.hasDog = false
}
)
Or going back to your original example Part B, you can put any logic you want in the lambda because it's like any other function. You don't have to do anything with the receiver, or you can do all kinds of stuff with it, and unrelated stuff, too.
config {
age = 42
println(this) // prints the toString of the UserConfig receiver instance
repeat(3) { iteration ->
println(copy(age = iteration * 4)) // prints copies of receiver
}
(1..10).forEach {
println(it)
if (it == 5) {
println("5 is great!")
}
}
hasDog = false
println("I return Unit.")
}
I have a code that looks like the following:
infix fun <T> Option<T>.valueIs(value : T): Pair<() -> Boolean,Set<Node>> {
val function = {this.selectedValue == value}
val parents = setOf(this)
return Pair(function, parents)
}
My question is if Kotlin will always create an anonymous object in val function = {this.selectedValue == value} in the JVM every time that this extension function is called or if it has some sort of optimization to reuse it if this and value are the same.
Kotlin, like Java, can avoid creating a new object each time if your lambda doesn't access (also called "capture") variables declared outside it (including this); {this.selectedValue == value} captures this and value, so it doesn't.
You could imagine some cache mapping captured variables to lambda instances, so it's effectively
private val lambdas = mutableMapOf<Any, () -> Boolean>()
infix fun <T> Option<T>.valueIs(value : T): Pair<() -> Boolean,Set<Node>> {
val function = lambdas.getOrUpdate(Pair(this, value)) {this.selectedValue == value}
val parents = setOf(this)
return Pair(function, parents)
}
but:
it prevents lambdas from being garbage-collected just in case you'll call the method with the same this and value later (could be fixed by using WeakHashMap);
it's non-trivial overhead even neglecting that;
it requires any captured values to have well-behaved hashCode and equals. Ok, they should have them anyway, but just imagine problems from debugging this if they don't!
Kotlin has another very important way to avoid creating objects for lambdas: passing them as arguments to inline functions. Of course it isn't applicable when you want to put your lambda into a data structure (even one as simple as Pair) or just return it.
Decompiling the bytecode:
#NotNull
public static final Pair valueIs(#NotNull final Option $this$valueIs, final Object value) {
Intrinsics.checkParameterIsNotNull($this$valueIs, "$this$valueIs");
Function0 function = (Function0)(new Function0() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke() {
return this.invoke();
}
public final boolean invoke() {
return Intrinsics.areEqual($this$valueIs.getSelectedValue(), value);
}
});
Set parents = SetsKt.setOf($this$valueIs);
return new Pair(function, parents);
}
As we can see, the lambda creates a new object Function0 every time this function is called.
So, every time this function is called. It seems that a new object will be created.
What is the difference between with and apply. From what I know the following code does the same thing:
swingElement.apply {
minWidth = ENABLED_COLUMN_WIDTH
maxWidth = ENABLED_COLUMN_WIDTH
preferredWidth = ENABLED_COLUMN_WIDTH
}
with(swingElement) {
minWidth = ENABLED_COLUMN_WIDTH
maxWidth = ENABLED_COLUMN_WIDTH
preferredWidth = ENABLED_COLUMN_WIDTH
}
Is there any difference and should I use one over the other?
Also, are there some cases where one would work and the other won't?
There're two differences:
apply accepts an instance as the receiver while with requires an instance to be passed as an argument. In both cases the instance will become this within a block.
apply returns the receiver and with returns a result of the last expression within its block.
I'm not sure there can be some strict rules on which function to choose. Usually you use apply when you need to do something with an object and return it. And when you need to perform some operations on an object and return some other object you can use either with or run. I prefer run because it's more readable in my opinion but it's a matter of taste.
Here are the Similarities and Differences
Similarities
With and Apply both accept an object as a receiver in whatever manner they are passed.
Differences
With returns the last line in the lambda as the result of the expression.
Apply returns the object that was passed in as the receiver as the result of the lambda expression.
Examples
With
private val ORIENTATIONS = with(SparseIntArray()) {
append(Surface.ROTATION_0, 90)
append(Surface.ROTATION_90, 0)
append(Surface.ROTATION_180, 270)
append(Surface.ROTATION_270, 180)
}
ORIENTATIONS[0] // doesn't work
// Here, using with prevents me from accessing the items in the SparseArray because,
// the last line actually returns nothing
Apply
private val ORIENTATIONS = SparseIntArray().apply {
append(Surface.ROTATION_0, 90)
append(Surface.ROTATION_90, 0)
append(Surface.ROTATION_180, 270)
append(Surface.ROTATION_270, 180)
}
ORIENTATIONS[0] // Works
// Here, using apply, allows me to access the items in the SparseArray because,
// the SparseArray is returned as the result of the expression
The apply function
//returns receiver T, T exposed as `this`
fun <T> T.apply(block: T.() -> Unit): T
Description
The apply function is invoked on a receiver T, which will be exposed as this in the passed lambda expression. The receiver also becomes the result of apply automatically.
The with function
//return arbitrary value R, not an extension function, T exposed as `this`
fun <T, R> with(receiver: T, block: T.() -> R): R
Description
The with function, as opposed to all other scope functions (let, run, also, apply), is not defined as an extension function. Instead, the function is invoked with a receiver object as its first argument explicitly. Same as apply, the receiver is exposed as this in the passed lambda. The result of the lambda, i.e. it’s last statement, becomes the result (R) of with.
"with(here class reference required)" is used for accessing variable of another class but not for method of that class.
Now if we want to use variable and method of another class that time we need to use apply(reference.apply{}) Declare a class like below
class Employee {
var name:String = ""
var age:Int = -1
fun customMethod() {
println("I am kotlin developer")
}
}
Now we can access name and age variable of Employee class in onCreate by "with"
val emp = Employee()
with(emp) {
name="Shri Ram"
age=30
}
println(emp.name)
println(emp.age)}
but we cannot access the "customMethod" of Employee class by with so if we need to use variable along with method, then we need to use "apply":
val emp = Employee()
emp.apply {
name="param"
age=30
}.customMethod()
println(emp.name)
println(emp.age)}
Output of with
I/System.out: Shri Ram
I/System.out: 30
Output of apply
I/System.out: I am kotlin developer
I/System.out: param
I/System.out: 30
Basically "with" will require one object and that will return only last line. But "apply" do for you like in below example..
val myRectangle = Rectangle().apply {
length = 4
breadth = 5
color = 0xFAFAFA}
This is useful for configuring properties that aren't present in the object constructor.
val obj = with(Turtle()){
penDown()
penUp()
"test"
}
println("test will print "+obj)
Differences
Apply: Return the object that passes as an argument
With: Return the object in the last line of code
#Test
fun `apply vs with`() {
val list: MutableList<Int> = mutableListOf<Int>().apply {
add(1)
add(2)
add(3)
}
assertEquals(3, list.size)
val number: Int = with(mutableListOf<Int>()) {
add(1)
add(2)
get(0)
}
assertEquals(1, number)
}
When to use apply or with?
Kotlin documentation explains the usages here, but in my opinion, I find it useful when you don't want to type the object repeatedly.
Without apply or with
pluginManager.apply("com.example")
pluginManager.apply("com.android.test")
pluginManager.apply("org.jetbrains.kotlin.android")
With with or apply. Notice that I don't need to type pluginManager three times.
with(pluginManager) {
apply("com.example")
apply("com.android.test")
apply("org.jetbrains.kotlin.android")
}
pluginManager.apply {
apply("com.example")
apply("com.android.test")
apply("org.jetbrains.kotlin.android")
}
I wish to have a good example for each function run, let, apply, also, with
I have read this article but still lack of an example
All these functions are used for switching the scope of the current function / the variable. They are used to keep things that belong together in one place (mostly initializations).
Here are some examples:
run - returns anything you want and re-scopes the variable it's used on to this
val password: Password = PasswordGenerator().run {
seed = "someString"
hash = {s -> someHash(s)}
hashRepetitions = 1000
generate()
}
The password generator is now rescoped as this and we can therefore set seed, hash and hashRepetitions without using a variable.
generate() will return an instance of Password.
apply is similar, but it will return this:
val generator = PasswordGenerator().apply {
seed = "someString"
hash = {s -> someHash(s)}
hashRepetitions = 1000
}
val pasword = generator.generate()
That's particularly useful as a replacement for the Builder pattern, and if you want to re-use certain configurations.
let - mostly used to avoid null checks, but can also be used as a replacement for run. The difference is, that this will still be the same as before and you access the re-scoped variable using it:
val fruitBasket = ...
apple?.let {
println("adding a ${it.color} apple!")
fruitBasket.add(it)
}
The code above will add the apple to the basket only if it's not null. Also notice that it is now not optional anymore so you won't run into a NullPointerException here (aka. you don't need to use ?. to access its attributes)
also - use it when you want to use apply, but don't want to shadow this
class FruitBasket {
private var weight = 0
fun addFrom(appleTree: AppleTree) {
val apple = appleTree.pick().also { apple ->
this.weight += apple.weight
add(apple)
}
...
}
...
fun add(fruit: Fruit) = ...
}
Using apply here would shadow this, so that this.weight would refer to the apple, and not to the fruit basket.
Note: I shamelessly took the examples from my blog
There are a few more articles like here, and here that are worth to take a look.
I think it is down to when you need a shorter, more concise within a few lines, and to avoid branching or conditional statement checking (such as if not null, then do this).
I love this simple chart, so I linked it here. You can see it from this as written by Sebastiano Gottardo.
Please also look at the chart accompanying my explanation below.
Concept
I think it as a role playing way inside your code block when you call those functions + whether you want yourself back (to chain call functions, or set to result variable, etc).
Above is what I think.
Concept Example
Let's see examples for all of them here
1.) myComputer.apply { } means you want to act as a main actor (you want to think that you're computer), and you want yourself back (computer) so you can do
var crashedComputer = myComputer.apply {
// you're the computer, you yourself install the apps
// note: installFancyApps is one of methods of computer
installFancyApps()
}.crash()
Yup, you yourself just install the apps, crash yourself, and saved yourself as reference to allow others to see and do something with it.
2.) myComputer.also {} means you're completely sure you aren't computer, you're outsider that wants to do something with it, and also wants it computer as a returned result.
var crashedComputer = myComputer.also {
// now your grandpa does something with it
myGrandpa.installVirusOn(it)
}.crash()
3.) with(myComputer) { } means you're main actor (computer), and you don't want yourself as a result back.
with(myComputer) {
// you're the computer, you yourself install the apps
installFancyApps()
}
4.) myComputer.run { } means you're main actor (computer), and you don't want yourself as a result back.
myComputer.run {
// you're the computer, you yourself install the apps
installFancyApps()
}
but it's different from with { } in a very subtle sense that you can chain call run { } like the following
myComputer.run {
installFancyApps()
}.run {
// computer object isn't passed through here. So you cannot call installFancyApps() here again.
println("woop!")
}
This is due to run {} is extension function, but with { } is not. So you call run { } and this inside the code block will be reflected to the caller type of object. You can see this for an excellent explanation for the difference between run {} and with {}.
5.) myComputer.let { } means you're outsider that looks at the computer, and want to do something about it without any care for computer instance to be returned back to you again.
myComputer.let {
myGrandpa.installVirusOn(it)
}
The Way to Look At It
I tend to look at also and let as something which is external, outside. Whenever you say these two words, it's like you try to act up on something. let install virus on this computer, and also crash it. So this nails down the part of whether you're an actor or not.
For the result part, it's clearly there. also expresses that it's also another thing, so you still retain the availability of object itself. Thus it returns it as a result.
Everything else associates with this. Additionally run/with clearly doesn't interest in return object-self back. Now you can differentiate all of them.
I think sometimes when we step away from 100% programming/logic-based of examples, then we are in better position to conceptualize things. But that depends right :)
There are 6 different scoping functions:
T.run
T.let
T.apply
T.also
with
run
I prepared a visual note as the below to show the differences :
data class Citizen(var name: String, var age: Int, var residence: String)
Decision depends on your needs. The use cases of different functions overlap, so that you can choose the functions based on the specific conventions used in your project or team.
Although the scope functions are a way of making the code more concise, avoid overusing them: it can decrease your code readability and lead to errors. Avoid nesting scope functions and be careful when chaining them: it's easy to get confused about the current context object and the value of this or it.
Here is another diagram for deciding which one to use from https://medium.com/#elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84
Some conventions are as the following :
Use also for additional actions that don't alter the object, such as logging or printing debug information.
val numbers = mutableListOf("one", "two", "three")
numbers
.also { println("The list elements before adding new one: $it") }
.add("four")
The common case for apply is the object configuration.
val adam = Person("Adam").apply {
age = 32
city = "London"
}
println(adam)
If you need shadowing, use run
fun test() {
var mood = "I am sad"
run {
val mood = "I am happy"
println(mood) // I am happy
}
println(mood) // I am sad
}
If you need to return receiver object itself, use apply or also
let, also, apply, takeIf, takeUnless are extension functions in Kotlin.
To understand these function you have to understand Extension functions and Lambda functions in Kotlin.
Extension Function:
By the use of extension function, we can create a function for a class without inheriting a class.
Kotlin, similar to C# and Gosu, provides the ability to extend a class
with new functionality without having to inherit from the class or use
any type of design pattern such as Decorator. This is done via special
declarations called extensions. Kotlin supports extension functions
and extension properties.
So, to find if only numbers in the String, you can create a method like below without inheriting String class.
fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())
you can use the above extension function like this,
val phoneNumber = "8899665544"
println(phoneNumber.isNumber)
which is prints true.
Lambda Functions:
Lambda functions are just like Interface in Java. But in Kotlin, lambda functions can be passed as a parameter in functions.
Example:
fun String.isNumber(block: () -> Unit): Boolean {
return if (this.matches("[0-9]+".toRegex())) {
block()
true
} else false
}
You can see, the block is a lambda function and it is passed as a parameter. You can use the above function like this,
val phoneNumber = "8899665544"
println(phoneNumber.isNumber {
println("Block executed")
})
The above function will print like this,
Block executed
true
I hope, now you got an idea about Extension functions and Lambda functions. Now we can go to Extension functions one by one.
let
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
Two Types T and R used in the above function.
T.let
T could be any object like String class. so you can invoke this function with any objects.
block: (T) -> R
In parameter of let, you can see the above lambda function. Also, the invoking object is passed as a parameter of the function. So you can use the invoking class object inside the function. then it returns the R (another object).
Example:
val phoneNumber = "8899665544"
val numberAndCount: Pair<Int, Int> = phoneNumber.let { it.toInt() to it.count() }
In above example let takes String as a parameter of its lambda function and it returns Pair in return.
In the same way, other extension function works.
also
public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
extension function also takes the invoking class as a lambda function parameter and returns nothing.
Example:
val phoneNumber = "8899665544"
phoneNumber.also { number ->
println(number.contains("8"))
println(number.length)
}
apply
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
Same as also but the same invoking object passed as the function so you can use the functions and other properties without calling it or parameter name.
Example:
val phoneNumber = "8899665544"
phoneNumber.apply {
println(contains("8"))
println(length)
}
You can see in the above example the functions of String class directly invoked inside the lambda funtion.
takeIf
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null
Example:
val phoneNumber = "8899665544"
val number = phoneNumber.takeIf { it.matches("[0-9]+".toRegex()) }
In above example number will have a string of phoneNumber only it matches the regex. Otherwise, it will be null.
takeUnless
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null
It is the reverse of takeIf.
Example:
val phoneNumber = "8899665544"
val number = phoneNumber.takeUnless { it.matches("[0-9]+".toRegex()) }
number will have a string of phoneNumber only if not matches the regex. Otherwise, it will be null.
You can view similar answers which is usefull here difference between kotlin also, apply, let, use, takeIf and takeUnless in Kotlin
According to my experience, since such functions are inline syntactic sugar with no performance difference, you should always choose the one that requires writing the least amount of code in the lamda.
To do this, first determine whether you want the lambda to return its result (choose run/let) or the object itself (choose apply/also); then in most cases when the lambda is a single expression, choose the ones with the same block function type as that expression, because when it's a receiver expression, this can be omitted, when it's a parameter expression, it is shorter than this:
val a: Type = ...
fun Type.receiverFunction(...): ReturnType { ... }
a.run/*apply*/ { receiverFunction(...) } // shorter because "this" can be omitted
a.let/*also*/ { it.receiverFunction(...) } // longer
fun parameterFunction(parameter: Type, ...): ReturnType { ... }
a.run/*apply*/ { parameterFunction(this, ...) } // longer
a.let/*also*/ { parameterFunction(it, ...) } // shorter because "it" is shorter than "this"
However, when the lambda consists of a mix of them, it's up to you then to choose the one that fits better into the context or you feel more comfortable with.
Also, use the ones with parameter block function when deconstruction is needed:
val pair: Pair<TypeA, TypeB> = ...
pair.run/*apply*/ {
val (first, second) = this
...
} // longer
pair.let/*also*/ { (first, second) -> ... } // shorter
Here is a brief comparison among all these functions from JetBrains's official Kotlin course on Coursera Kotlin for Java Developers:
I must admit that the difference is not so obvious at first glance, among other things because these 5 functions are often interchangeable. Here is my understanding :
APPLY -> Initialize an object with theses properties and wait for the object
val paint = Paint().apply {
this.style = Paint.Style.FILL
this.color = Color.WHITE
}
LET -> Isolate a piece of code and wait for the result
val result = let {
val b = 3
val c = 2
b + c
}
or
val a = 1
val result = a.let {
val b = 3
val c = 2
it + b + c
}
or
val paint: Paint? = Paint()
paint?.let {
// here, paint is always NOT NULL
// paint is "Paint", not "Paint?"
}
ALSO -> Execute 2 operations at the same time and wait for the result
var a = 1
var b = 3
a = b.also { b = a }
WITH -> Do something with this variable/object and don't wait for a result (chaining NOT allowed )
with(canvas) {
this.draw(x)
this.draw(y)
}
RUN -> Do something with this variable/object and don't wait for a result (chaining allowed)
canvas.run {
this.draw(x)
this.draw(y)
}
or
canvas.run {this.draw(x)}.run {this.draw(x)}
What is the difference between with and apply. From what I know the following code does the same thing:
swingElement.apply {
minWidth = ENABLED_COLUMN_WIDTH
maxWidth = ENABLED_COLUMN_WIDTH
preferredWidth = ENABLED_COLUMN_WIDTH
}
with(swingElement) {
minWidth = ENABLED_COLUMN_WIDTH
maxWidth = ENABLED_COLUMN_WIDTH
preferredWidth = ENABLED_COLUMN_WIDTH
}
Is there any difference and should I use one over the other?
Also, are there some cases where one would work and the other won't?
There're two differences:
apply accepts an instance as the receiver while with requires an instance to be passed as an argument. In both cases the instance will become this within a block.
apply returns the receiver and with returns a result of the last expression within its block.
I'm not sure there can be some strict rules on which function to choose. Usually you use apply when you need to do something with an object and return it. And when you need to perform some operations on an object and return some other object you can use either with or run. I prefer run because it's more readable in my opinion but it's a matter of taste.
Here are the Similarities and Differences
Similarities
With and Apply both accept an object as a receiver in whatever manner they are passed.
Differences
With returns the last line in the lambda as the result of the expression.
Apply returns the object that was passed in as the receiver as the result of the lambda expression.
Examples
With
private val ORIENTATIONS = with(SparseIntArray()) {
append(Surface.ROTATION_0, 90)
append(Surface.ROTATION_90, 0)
append(Surface.ROTATION_180, 270)
append(Surface.ROTATION_270, 180)
}
ORIENTATIONS[0] // doesn't work
// Here, using with prevents me from accessing the items in the SparseArray because,
// the last line actually returns nothing
Apply
private val ORIENTATIONS = SparseIntArray().apply {
append(Surface.ROTATION_0, 90)
append(Surface.ROTATION_90, 0)
append(Surface.ROTATION_180, 270)
append(Surface.ROTATION_270, 180)
}
ORIENTATIONS[0] // Works
// Here, using apply, allows me to access the items in the SparseArray because,
// the SparseArray is returned as the result of the expression
The apply function
//returns receiver T, T exposed as `this`
fun <T> T.apply(block: T.() -> Unit): T
Description
The apply function is invoked on a receiver T, which will be exposed as this in the passed lambda expression. The receiver also becomes the result of apply automatically.
The with function
//return arbitrary value R, not an extension function, T exposed as `this`
fun <T, R> with(receiver: T, block: T.() -> R): R
Description
The with function, as opposed to all other scope functions (let, run, also, apply), is not defined as an extension function. Instead, the function is invoked with a receiver object as its first argument explicitly. Same as apply, the receiver is exposed as this in the passed lambda. The result of the lambda, i.e. it’s last statement, becomes the result (R) of with.
"with(here class reference required)" is used for accessing variable of another class but not for method of that class.
Now if we want to use variable and method of another class that time we need to use apply(reference.apply{}) Declare a class like below
class Employee {
var name:String = ""
var age:Int = -1
fun customMethod() {
println("I am kotlin developer")
}
}
Now we can access name and age variable of Employee class in onCreate by "with"
val emp = Employee()
with(emp) {
name="Shri Ram"
age=30
}
println(emp.name)
println(emp.age)}
but we cannot access the "customMethod" of Employee class by with so if we need to use variable along with method, then we need to use "apply":
val emp = Employee()
emp.apply {
name="param"
age=30
}.customMethod()
println(emp.name)
println(emp.age)}
Output of with
I/System.out: Shri Ram
I/System.out: 30
Output of apply
I/System.out: I am kotlin developer
I/System.out: param
I/System.out: 30
Basically "with" will require one object and that will return only last line. But "apply" do for you like in below example..
val myRectangle = Rectangle().apply {
length = 4
breadth = 5
color = 0xFAFAFA}
This is useful for configuring properties that aren't present in the object constructor.
val obj = with(Turtle()){
penDown()
penUp()
"test"
}
println("test will print "+obj)
Differences
Apply: Return the object that passes as an argument
With: Return the object in the last line of code
#Test
fun `apply vs with`() {
val list: MutableList<Int> = mutableListOf<Int>().apply {
add(1)
add(2)
add(3)
}
assertEquals(3, list.size)
val number: Int = with(mutableListOf<Int>()) {
add(1)
add(2)
get(0)
}
assertEquals(1, number)
}
When to use apply or with?
Kotlin documentation explains the usages here, but in my opinion, I find it useful when you don't want to type the object repeatedly.
Without apply or with
pluginManager.apply("com.example")
pluginManager.apply("com.android.test")
pluginManager.apply("org.jetbrains.kotlin.android")
With with or apply. Notice that I don't need to type pluginManager three times.
with(pluginManager) {
apply("com.example")
apply("com.android.test")
apply("org.jetbrains.kotlin.android")
}
pluginManager.apply {
apply("com.example")
apply("com.android.test")
apply("org.jetbrains.kotlin.android")
}