Create a list from statements in a DSL block - kotlin

In an attempt to build a DSL for validation, I am looking for ways to collect the statements inside a block/lambda with receiver. To illustrate, here is a minimal example without the actual validation logic:
data class Constraint(val hint: String)
class Validation(val constraints: List<Constraint>) {
companion object {
operator fun invoke(init: (ValidationBuilder.() -> Unit)): Validation {
return ValidationBuilder().apply(init).build()
}
}
class ValidationBuilder {
private var constraints: MutableList<Constraint> = mutableListOf()
operator fun Constraint.unaryPlus() {
constraints.add(this)
}
fun build() = Validation(constraints)
}
}
This can then be used to build a Validation like so
val validation = Validation {
+Constraint("First constraint")
val secondConstraintHint = "Second constraint"
+Constraint(secondConstraintHint)
}
I would like to get rid of the unaryPlus operator and directly collect the individual statements in the block that are evaluated to a Constraint so that I can do something like:
val validation = Validation {
Constraint("First constraint")
val secondConstraintHint = "Second constraint"
Constraint(secondConstraintHint)
}
Is that possible somehow?
To give a little bit more context, the actual result I am aiming for will look more like this:
Validation<User> {
User::firstName {
val min = 2
minLength(min) hint "Please provide a first name"
maxLength(200) // uses default hint
}
}

Well, there seems to be no straightforward solution, because Kotlin provides no way to handle an evaluated expression result that is not assigned, returned or passed anywhere.
A possible workaround is to mimic the constructor you need with a function defined for your builder:
class ValidationBuilder {
/* ... */
fun Constraint(name: String) =
full.qualified.name.of.Constraint(name).also(constraints::add)
}
Unfortunately, this will require you to duplicate all the signatures that you want to call in this way.
UPD (answering to the comment): I believe the idiomatic way for users to customize a DSL is defining their own extensions for the DSL builders:
fun ValidationBuilder.nonEmptyText(min: Int = 1, max: Int = 65.536) = TODO()
If a Constraint that comes from outside the DSL is an important use case, you can cover it with a special function (e.g. fun ValidationBuilder.constraint(...)) and let the users delegate their extensions to it.

Related

Providing only one type parameter to an extension function with multiple type parameters in Kotlin

Introduction
In Kotlin I have a generic conversion extension function that simplifies conversion of this object of type C to an object of another type T (declared as the receiver) with additional conversion action that treats receiver as this and also provides access to original object:
inline fun <C, T, R> C.convertTo(receiver: T, action: T.(C) -> R) = receiver.apply {
action(this#convertTo)
}
It is used like this:
val source: Source = Source()
val result = source.convertTo(Result()) {
resultValue = it.sourceValue
// and so on...
}
I noticed I often use this function on receivers that are created by parameterless constructors and thought it would be nice to simplify it even more by creating additional version of convertTo() that automates construction of the receiver based on its type, like this:
inline fun <reified T, C, R> C.convertTo(action: T.(C) -> R) = with(T::class.constructors.first().call()) {
convertTo(this, action) // calling the first version of convertTo()
}
Unfortunately, I cannot call it like this:
source.convertTo<Result>() {}
because Kotlin expects three type parameters provided.
Question
Given above context, is it possible in Kotlin to create a generic function with multiple type parameters that accepts providing just one type parameter while other types are determined from the call-site?
Additional examples (by #broot)
Imagine there is no filterIsInstance() in stdlib and we would like to implement it (or we are the developer of stdlib). Assume we have access to #Exact as this is important for our example. It would be probably the best to declare it as:
inline fun <T, reified V : T> Iterable<#Exact T>.filterTyped(): List<V>
Now, it would be most convenient to use it like this:
val dogs = animals.filterTyped<Dog>() // compile error
Unfortunately, we have to use one of workarounds:
val dogs = animals.filterTyped<Animal, Dog>()
val dogs: List<Dog> = animals.filterTyped()
The last one isn't that bad.
Now, we would like to create a function that looks for items of a specific type and maps them:
inline fun <T, reified V : T, R> Iterable<T>.filterTypedAndMap(transform: (V) -> R): List<R>
Again, it would be nice to use it just like this:
animals.filterTypedAndMap<Dog> { it.barkingVolume } // compile error
Instead, we have this:
animals.filterTypedAndMap<Animal, Dog, Int> { it.barkingVolume }
animals.filterTypedAndMap { dog: Dog -> dog.barkingVolume }
This is still not that bad, but the example is intentionally relatively simple to make it easy to understand. In reality the function would be more complicated, would have more typed params, lambda would receive more arguments, etc. and then it would become hard to use. After receiving the error about type inference, the user would have to read the definition of the function thoroughly to understand, what is missing and where to provide explicit types.
As a side note: isn't it strange that Kotlin disallows code like this: cat is Dog, but allows this: cats.filterIsInstance<Dog>()? Our own filterTyped() would not allow this. So maybe (but just maybe), filterIsInstance() was designed like this exactly because of the problem described in this question (it uses * instead of additional T).
Another example, utilizing already existing reduce() function. We have function like this:
operator fun Animal.plus(other: Animal): Animal
(Don't ask, it doesn't make sense)
Now, reducing a list of dogs seems pretty straightforward:
dogs.reduce { acc, item -> acc + item } // compile error
Unfortunately, this is not possible, because compiler does not know how to properly infer S to Animal. We can't easily provide S only and even providing the return type does not help here:
val animal: Animal = dogs.reduce { acc, item -> acc + item } // compile error
We need to use some awkward workarounds:
dogs.reduce<Animal, Dog> { acc, item -> acc + item }
(dogs as List<Animal>).reduce { acc, item -> acc + item }
dogs.reduce { acc: Animal, item: Animal -> acc + item }
The type parameter R is not necessary:
inline fun <C, T> C.convertTo(receiver: T, action: T.(C) -> Unit) = receiver.apply {
action(this#convertTo)
}
inline fun <reified T, C> C.convertTo(action: T.(C) -> Unit) = with(T::class.constructors.first().call()) {
convertTo(this, action) // calling the first version of convertTo()
}
If you use Unit, even if the function passed in has a non-Unit return type, the compiler still allows you to pass that function.
And there are other ways to help the compiler infer the type parameters, not only by directly specifying them in <>. You can also annotate the variable's result type:
val result: Result = source.convertTo { ... }
You can also change the name of convertTo to something like convert to make it more readable.
Another option is:
inline fun <T: Any, C> C.convertTo(resultType: KClass<T>, action: T.(C) -> Unit) = with(resultType.constructors.first().call()) {
convertTo(this, action)
}
val result = source.convertTo(Result::class) { ... }
However, this will conflict with the first overload. So you have to resolve it somehow. You can rename the first overload, but I can't think of any good names off the top of my head. I would suggest that you specify the parameter name like this
source.convertTo(resultType = Result::class) { ... }
Side note: I'm not sure if the parameterless constructor is always the first in the constructors list. I suggest that you actually find the parameterless constructor.
This answer does not solve the stated problem but incorporates input from #Sweeper to provide a workaround at least simplifying result object instantiation.
First of all, the main stated problem can be somewhat mitigated if we explicitly state variable's result type (i.e. val result: Result = source.convertTo {}) but it's not enough to solve the problem in cases described by #broot.
Secondly, using KClass<T> as result parameter type provides ability to use KClass<T>.createInstance() making sure we find a parameterless constructor (if there's any – if there is none, then result-instantiating convertTo() is not eligible for use). We can also benefit from Kotlin's default parameter values to make result parameter type omittable from calls, we just need to take into account that action might be provided as lambda (last parameter of call) or function reference – this will require two versions of result-instantiating convertTo().
So, taking all the above into account, I've come up with this implementation(s) of convertTo():
// version A: basic, expects explicitly provided instance of `receiver`
inline fun <C, T> C.convertTo(receiver: T, action: T.(C) -> Unit) = receiver.apply {
action(this#convertTo)
}
// version B: can instantiate result of type `T`, supports calls where `action` is a last lambda
inline fun <C, reified T : Any> C.convertTo(resultType: KClass<T> = T::class, action: T.(C) -> Unit) = with(resultType.createInstance()) {
(this#convertTo).convertTo(this#with, action)
}
// version C: can instantiate result of type `T`, supports calls where `action` is passed by reference
inline fun <C, reified T : Any> C.convertTo(action: T.(C) -> Unit, resultType: KClass<T> = T::class) = with(resultType.createInstance()) {
(this#convertTo).convertTo(T::class, action)
}
All three versions work together depending on a specific use case. Below is a set of examples explaining what version is used in what case.
class Source { var sourceId = "" }
class Result { var resultId = "" }
val source = Source()
fun convertX(result: Result, source: Source) {
result.resultId = source.sourceId
}
fun convertY(result: Result, source: Source) = true
fun Source.toResultX(): Result = convertTo { resultId = it.sourceId }
fun Source.toResultY(): Result = convertTo(::convertX)
val result0 = source.convertTo(Result()) { resultId = it.sourceId } // uses version A of convertTo()
val result1: Result = source.convertTo { resultId = it.sourceId } // uses version B of convertTo()
val result2: Result = source.convertTo(::convertX) // uses version C of convertTo()
val result3: Result = source.convertTo(::convertY) // uses version C of convertTo()
val result4: Result = source.toResultX() // uses version B of convertTo()
val result5: Result = source.toResultY() // uses version C of convertTo()
P.S.: As #Sweeper notices, convertTo might not be a good name for the result-instantiating versions (as it's not as readable as with basic version) but that's a secondary problem.

How to write custom chaining

I want to create custom chainig to prevent some repetition.
I am a bit unclear on how to define it
I have a lot of :
someLiveData.observe(this) { objectWithTextAndVisibility->
textView.text = objectWithTextAndVisibility.text
textView.visibility = objectWithTextAndVisibility.visibility
}
i want to write something that will look as follows
someLiveData.observe(this).bind(textView).on(text)
and it will do the same thing
is there a way to define this ?
If I understood your question correctly, a generic structure like this may achieve what you want to do:
infix fun <T> LiveData<T>.observe(owner: LifecycleOwner) = LiveDataHolder(this, owner)
class LiveDataHolder<T>(val liveData: LiveData<T>, val owner: LifecycleOwner)
infix fun <T, S> LiveDataHolder<T>.bind(subject: S) = LiveDataBinder(this, subject)
class LiveDataBinder<T, S>(val liveDataHolder: LiveDataHolder<T>, val subject: S)
infix fun <T : VisibilityCarrier> LiveDataBinder<T, TextView>.on(textSelector: (T) -> String) {
liveDataHolder.liveData.observe(liveDataHolder.owner) {
subject.text = textSelector(it)
subject.visibility = it.visibility
}
}
interface VisibilityCarrier {
val visibility: Int
}
This should give you good flexibility and prevent a lot of repetition, however at the cost of adding some obscurity to your code base, and not conveying what it does right alway.
Additional and different behaviors can be added by implementing new LiveDataBinder extension functions, like the on function, even for different kinds of objects and expected interfaces (instead of only this combination of TextView and VisibilityCarrier, even by maintaining the on name) and so on.
The use style can vary a lot with this, being like the ones below (considering Data implements VisibilityCarrier interface and provides a text String property):
liveData.observe(this).bind(textView).on { it.text }
liveData.observe(this) bind textView on { it.text }
liveData.observe(this).bind(textView).on(Data::text)
liveData.observe(this) bind textView on Data::text
This on implementation also allows for defining which property to use as text and different classes as well, like so:
anotherLiveData.observe(this).bind(textView).on(AnotherData::someText)
anotherLiveData.observe(this).bind(textView).on(AnotherData::anotherText)
UPDATE: After reading the explanation in the comments I think I got it, and believe this should address the point (where Data contains text and visibility properties):
infix fun <T : Data, S : View> LiveDataBinder<T, out S>.on(textProperty: KMutableProperty1<S, in String>) {
liveDataHolder.liveData.observe(liveDataHolder.owner) {
textProperty.set(subject, it.text)
subject.visibility = it.visibility
}
}
In the case of TextView and other Java defined classes, compiler will complain about synthetic access, and this problem could be addressed like in the following snippet (Kotlin views should be fine without this kind of workaround):
var TextView.text_: CharSequence
get() = text
set(value) { text = value }
And the usage would be like:
liveData.observe(this).bind(textView).on(TextView::text_)
liveData.observe(this).bind(customView).on(CustomView::someText)
UPDATE 2: A better approach as suggested by Tenfour04 (thanks). The following will avoid the synthetic property access compiler error:
infix fun <T : Data, S : View> LiveDataBinder<T, S>.on(textProperty: S.(String) -> Unit) {
liveDataHolder.liveData.observe(liveDataHolder.owner) {
subject.textProperty(it.text)
subject.visibility = it.visibility
}
}
And the usage would be like (without needing the text_ property extension):
liveData.observe(owner).bind(textView).on(TextView::setText)
After reading your comments on the other answer, I think I see what you're trying to do.
If I'm correct, you want to use the builder pattern to first bind something that is set (a setter), and then specify a getter/mapper of the data type to get some sub-data type that is applied with that setter.
So you can set up a couple of intermediate classes to do it like this:
fun <T> LiveData<T>.observe(owner: LifecycleOwner) = BindableObserver<T>().also { observe(owner, it) }
class BindableObserver<D>: Observer<D> {
private var boundSetter: BoundSetter<D, *>? = null
fun <S> bind(setter: (S)->Unit) = BoundSetter<D, S>(setter).also { boundSetter = it }
override fun onChanged(t: D) {
boundSetter?.execute(t)
}
}
class BoundSetter<D, S>(private val setter: (S)->Unit) {
private var dataGetter: ((D)->S)? = null
fun on(getter: (D)->S) {
dataGetter = getter
}
fun execute(newValue: D) {
val subData = dataGetter?.invoke(newValue) ?: return
setter.invoke(subData)
}
}
You can't simply pass a TextView to bind, because Kotlin won't know which property of TextView to set, so you pass the property using property syntax (::). Unfortunately, TextView has a bunch of setText() overloads, so you have to specify the input type as well.
Usage syntax would be like this:
someLiveData.observe(this)
.bind<String>(textView::setText)
.on(ObjectWithTextAndVisibility::text)
To avoid the need for specifying which function of a TextView to bind, you could add a helper function:
fun <D> BindableObserver<D>.bind(textView: TextView) = bind<String>(textView::setText)
and then usage would be closer to what you suggested:
someLiveData.observe(this)
.bind(textView)
.on(ObjectWithTextAndVisibility::text)
You could also use lambda syntax:
someLiveData.observe(this)
.bind(textView)
.on { it.text }

Can I convert the one line of also syntax into two lines with Kotlin?

The Code A is from https://github.com/mycwcgr/camera/blob/master/CameraXBasic/app/src/main/java/com/android/example/cameraxbasic/fragments/CameraFragment.kt
It's a little difficult to understand the also syntax for me, so I convert the Code A to the Code B.
I think that the Code B is the same as the Code A, right?
Code A
private fun bindCameraUseCases() {
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
...
}
Code B
private fun bindCameraUseCases() {
val metrics = DisplayMetrics()
viewFinder.display.getRealMetrics(metrics)
}
Yes, it is. What the also { } extension function does is perform actions defined in its block with the caller object as a parameter and return the caller.
val list = mutableListOf<Int>().also {
// the newly created empty `MutableList` is a parameter in this lambda
// and can be referred using the `it` identifier
it.add(1)
}
// is equivalent to
val list = mutableListOf<Int>()
list.add(1)
In this case, yes: your Code A and Code B do the same thing.  Within the lambda, it refers to the object that also was called on (the newly-created DisplayMetrics instance); and that's also what's returned.
And to answer your implied question: yes, in this case using also probably doesn't have much benefit!
It's more useful in the middle of a complex expression or return value, e.g.:
private fun getMetrics()
= DisplayMetrics().also{ println("Created metrics: $it") }
instead of:
private fun getMetrics(): DisplayMetrics {
val metrics = DisplayMetrics()
println("Created metrics: $metrics")
return metrics
}
Here it avoids an explicit local value, references to it, and an explicit return; once you're used to the idiom, it's simpler to read as well — especially when it's used for something like logging that's not part of the main program logic.
Kotlin's scoping functions (also, apply, let, run, with) can be a big confusing, but this page explains them fairly well.

Generic inline function

Let's say I have an object which helps me to deserialize other objects from storage:
val books: MutableList<Book> = deserializer.getBookList()
val persons: MutableList<Person> = deserializer.getPersonList()
The methods getBookList and getPersonList are extension functions I have written. Their logic is allmost the same so I thought I may can combine them into one method. My problem is the generic return type. The methods look like this:
fun DataInput.getBookList(): MutableList<Book> {
val list = mutableListOf<Book>()
val size = this.readInt()
for(i in 0 .. size) {
val item = Book()
item.readExternal(this)
list.add(item)
}
return list
}
Is there some Kotlin magic (maybe with inline functions) which I can use to detect the List type and generify this methods? I think the problem would be val item = T() which will not work for generic types, right? Or is this possible with inline functions?
You cannot call the constructor of a generic type, because the compiler can't guarantee that it has a constructor (the type could be from an interface). What you can do to get around this though, is to pass a "creator"-function as a parameter to your function. Like this:
fun <T> DataInput.getList(createT: () -> T): MutableList<T> {
val list = mutableListOf<T>()
val size = this.readInt()
for(i in 0 .. size) {
val item = createT()
/* Unless readExternal is an extension on Any, this function
* either needs to be passed as a parameter as well,
* or you need add an upper bound to your type parameter
* with <T : SomeInterfaceWithReadExternal>
*/
item.readExternal(this)
list.add(item)
}
return list
}
Now you can call the function like this:
val books: MutableList<Book> = deserializer.getList(::Book)
val persons: MutableList<Person> = deserializer.getList(::Person)
Note:
As marstran mentioned in a comment, this requires the class to have a zero-arg constructor to work, or it will throw an exception at runtime. The compiler will not warn you if the constructor doesn't exist, so if you pick this way, make sure you actually pass a class with a zero-arg constructor.
You can't initialize generic types, in Kotlin or Java. At least not in the "traditional" way. You can't do this:
val item = T()
In Java, you'd pass a Class<T> and get the constructor. Very basic example of that:
public <T> void x(Class<T> cls){
cls.getConstructor().newInstance(); // Obviously you'd do something with the return value, but this is just a dummy example
}
You could do the same in Kotlin, but Kotlin has a reified keyword that makes it slightly easier. This requires an inline function, which means you'd change your function to:
inline fun <reified T> DataInput.getBookList(): MutableList<T> { // Notice the `<reified T>`
val list = mutableListOf<T>() // Use T here
val size = this.readInt()
for(i in 0 .. size) {
// This is where the initialization happens; you get the constructor, and create a new instance.
// Also works with arguments, if you have any, but you used an empty one so I assume yours is empty
val item = T::class.java.getConstructor().newInstance()!!
item.readExternal(this) // However, this is tricky. See my notes below this code block
list.add(item)
}
return list
}
However, readExternal isn't present in Any, which will present problems. The only exception is if you have an extension function for either Any or a generic type with that name and input.
If it's specific to some classes, then you can't do it like this, unless you have a shared parent. For an instance:
class Book(){
fun readExternal(input: DataInput) { /*Foo bar */}
}
class Person(){
fun readExternal(input: DataInput) { /*Foo bar */}
}
Would not work. There's no shared parent except Any, and Any doesn't have readExternal. The method is manually defined in each of them.
You could create a shared parent, as an interface or abstract class (assuming there isn't one already), and use <reified T : TheSharedParent>, and you would have access to it.
You could of course use reflection, but it's slightly harder, and adds some exceptions you need to handle. I don't recommend doing this; I'd personally use a superclass.
inline fun <reified T> DataInput.getBookList(): MutableList<T> {
val list = mutableListOf<T>()
val size = this.readInt()
val method = try {
T::class.java.getMethod("readExternal", DataInput::class.java)
}catch(e: NoSuchMethodException){
throw RuntimeException()
}catch(e: SecurityException){
throw RuntimeException()// This could be done better; but error handling is up to you, so I'm just making a basic example
// The catch clauses are pretty self-explanatory; if something happens when trying to get the method itself,
// These two catch them
}
for(i in 0 .. size) {
val item: T = T::class.java.getConstructor().newInstance()!!
method.invoke(item, this)
list.add(item)
}
return list
}

Example of when should we use run, let, apply, also and with on Kotlin

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)}