Ejemplo de cuándo deberíamos usar ejecutar, dejar, aplicar, también y con en Kotlin

101

Deseo tener un buen ejemplo para cada función ejecutar, dejar, aplicar, también, con

He leído este artículo pero todavía me falta un ejemplo.

UmAnusorn
fuente

Respuestas:

122

Todas estas funciones se utilizan para cambiar el alcance de la función actual / la variable. Se utilizan para mantener las cosas que pertenecen juntas en un solo lugar (principalmente inicializaciones).

Aquí hay unos ejemplos:

run - devuelve todo lo que desee y cambia el alcance de la variable en la que se usa this

val password: Password = PasswordGenerator().run {
       seed = "someString"
       hash = {s -> someHash(s)}
       hashRepetitions = 1000

       generate()
   }

El generador de contraseñas ahora se modifica como thisy, por lo tanto seed, podemos configurarlo , hashy hashRepetitionssin usar una variable. generate()devolverá una instancia de Password.

applyes similar, pero devolverá this:

val generator = PasswordGenerator().apply {
       seed = "someString"
       hash = {s -> someHash(s)}
       hashRepetitions = 1000
   }
val pasword = generator.generate()

Eso es particularmente útil como reemplazo del patrón Builder, y si desea reutilizar ciertas configuraciones.

let: se usa principalmente para evitar comprobaciones nulas, pero también se puede usar como reemplazo de run. La diferencia es que thisseguirá siendo el mismo que antes y podrá acceder a la variable rediseñada mediante it:

val fruitBasket = ...

apple?.let {
  println("adding a ${it.color} apple!")
  fruitBasket.add(it)
}

El código anterior agregará la manzana a la canasta solo si no es nulo. También ittenga en cuenta que ahora ya no es opcional, por lo que no se encontrará con una NullPointerException aquí (también conocido como no necesita usar ?.para acceder a sus atributos)

also- úsalo cuando quieras usarlo apply, pero no quieras sombrearthis

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

Usar applyaquí representaría una sombra this, por lo que this.weightse referiría a la manzana y no a la canasta de frutas.


Nota: tomé descaradamente los ejemplos de mi blog.

Lovis
fuente
3
Para cualquiera como yo sorprendido por el primer código, la última línea de una lambda se considera un valor de retorno en Kotlin.
Jay Lee
62

Hay algunos artículos más como aquí y aquí que vale la pena echarle un vistazo.

Creo que se debe a cuando necesite una más corta, más concisa dentro de unas pocas líneas, y para evitar la verificación de declaraciones condicional o bifurcadas (por ejemplo, si no es nula, entonces haga esto).

Me encanta este gráfico simple, así que lo vinculé aquí. Puedes verlo en esto escrito por Sebastiano Gottardo.

ingrese la descripción de la imagen aquí

Mire también el cuadro que acompaña a mi explicación a continuación.

Concepto

Creo que es una forma de juego de roles dentro de su bloque de código cuando llama a esas funciones + si desea volver (para encadenar funciones de llamada, o establecer como variable de resultado, etc.).

Arriba está lo que pienso.

Ejemplo de concepto

Veamos ejemplos de todos ellos aquí.

1.) myComputer.apply { }significa que quieres actuar como actor principal (quieres pensar que eres una computadora), y quieres volver a ti mismo (computadora) para poder hacerlo

var crashedComputer = myComputer.apply { 
    // you're the computer, you yourself install the apps
    // note: installFancyApps is one of methods of computer
    installFancyApps() 
}.crash()

Sí, usted mismo solo instala las aplicaciones, se bloquea y se guarda como referencia para permitir que otros lo vean y hagan algo con él.

2.) myComputer.also {}significa que está completamente seguro de que no es una computadora, que es un extraño que quiere hacer algo con ella y que también la quiere como una computadora como resultado.

var crashedComputer = myComputer.also { 
    // now your grandpa does something with it
    myGrandpa.installVirusOn(it) 
}.crash()

3.) with(myComputer) { }significa que eres el actor principal (computadora) y no te quieres a ti mismo como resultado.

with(myComputer) {
    // you're the computer, you yourself install the apps
    installFancyApps()
}

4.) myComputer.run { }significa que eres el actor principal (computadora) y no te quieres a ti mismo como resultado.

myComputer.run {
    // you're the computer, you yourself install the apps
    installFancyApps()
}

pero es diferente de, with { }en un sentido muy sutil, que puedes hacer una llamada en cadena run { }como la siguiente

myComputer.run {
    installFancyApps()
}.run {
    // computer object isn't passed through here. So you cannot call installFancyApps() here again.
    println("woop!")
}

Esto se debe a run {}su función de extensión, pero with { }no lo es. Entonces, llama run { }y thisdentro del bloque de código se reflejará el tipo de objeto de la persona que llama. Puede ver esto para obtener una excelente explicación de la diferencia entre run {}y with {}.

5.) myComputer.let { }significa que eres un extraño que mira la computadora y quieres hacer algo al respecto sin preocuparse de que la instancia de la computadora te sea devuelta nuevamente.

myComputer.let {
    myGrandpa.installVirusOn(it)
}

La forma de verlo

Tiendo a mirar alsoy letcomo algo externo, externo. Cada vez que dices estas dos palabras, es como si intentaras actuar mal. letinstale virus en esta computadora y alsociérrelo. Así que esto aclara la parte de si eres actor o no.

En cuanto a los resultados, claramente está ahí. alsoexpresa que también es otra cosa, por lo que aún conserva la disponibilidad del objeto en sí. Por lo tanto, lo devuelve como resultado.

Todo lo demás se asocia con this. Además, run/withclaramente no le interesa devolver el yo del objeto. Ahora puedes diferenciarlos todos.

Creo que a veces cuando nos alejamos del 100% de programación / ejemplos basados ​​en la lógica, estamos en una mejor posición para conceptualizar las cosas. Pero eso depende bien :)

haxpor
fuente
1
El diagrama lo dice todo; Lo mejor por mucho.
Shukant Pal
Esta debería ser la respuesta más aceptada y votada
Segun Wahaab
8

let, also, apply, takeIf, takeA menos que sean funciones de extensión en Kotlin.

Para comprender estas funciones, debe comprender las funciones de extensión y las funciones de Lambda en Kotlin.

Función de extensión:

Mediante el uso de la función de extensión, podemos crear una función para una clase sin heredar una clase.

Kotlin, similar a C # y Gosu, brinda la capacidad de extender una clase con nueva funcionalidad sin tener que heredar de la clase o usar ningún tipo de patrón de diseño como Decorator. Esto se hace a través de declaraciones especiales llamadas extensiones. Kotlin admite funciones de extensión y propiedades de extensión.

Entonces, para encontrar solo números en String, puede crear un método como el siguiente sin heredar la Stringclase.

fun String.isNumber(): Boolean = this.matches("[0-9]+".toRegex())

puede usar la función de extensión anterior como esta,

val phoneNumber = "8899665544"
println(phoneNumber.isNumber)

que es impresiones true.

Funciones Lambda:

Las funciones Lambda son como la interfaz en Java. Pero en Kotlin, las funciones lambda se pueden pasar como un parámetro en funciones.

Ejemplo:

fun String.isNumber(block: () -> Unit): Boolean {
    return if (this.matches("[0-9]+".toRegex())) {
        block()
        true
    } else false
}

Como puede ver, el bloque es una función lambda y se pasa como parámetro. Puede usar la función anterior de esta manera,

val phoneNumber = "8899665544"
    println(phoneNumber.isNumber {
        println("Block executed")
    })

La función anterior se imprimirá así,

Block executed
true

Espero que ahora tengas una idea sobre las funciones de extensión y las funciones de Lambda. Ahora podemos ir a las funciones de Extensión una por una.

dejar

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

Dos tipos T y R utilizados en la función anterior.

T.let

Tpodría ser cualquier objeto como la clase String. para que pueda invocar esta función con cualquier objeto.

block: (T) -> R

En el parámetro de let, puede ver la función lambda anterior. Además, el objeto que invoca se pasa como parámetro de la función. Entonces puede usar el objeto de clase que invoca dentro de la función. luego devuelve el R(otro objeto).

Ejemplo:

val phoneNumber = "8899665544"
val numberAndCount: Pair<Int, Int> = phoneNumber.let { it.toInt() to it.count() }

En el ejemplo anterior, tomemos String como parámetro de su función lambda y devuelve Pair a cambio.

De la misma manera, funciona otra función de extensión.

además

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }

La función de extensión alsotoma la clase que invoca como un parámetro de función lambda y no devuelve nada.

Ejemplo:

val phoneNumber = "8899665544"
phoneNumber.also { number ->
    println(number.contains("8"))
    println(number.length)
 }

aplicar

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

Igual que también pero el mismo objeto de invocación pasado como la función para que pueda usar las funciones y otras propiedades sin llamarlo o el nombre del parámetro.

Ejemplo:

val phoneNumber = "8899665544"
phoneNumber.apply { 
    println(contains("8"))
    println(length)
 }

Puede ver en el ejemplo anterior las funciones de la clase String invocadas directamente dentro de la función lambda.

tomar si

public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? = if (predicate(this)) this else null

Ejemplo:

val phoneNumber = "8899665544"
val number = phoneNumber.takeIf { it.matches("[0-9]+".toRegex()) }

En el ejemplo anterior numbertendrá una cadena de phoneNumbersolo coincide con el regex. De lo contrario, lo será null.

tomar a menos

public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? = if (!predicate(this)) this else null

Es lo contrario de takeIf.

Ejemplo:

val phoneNumber = "8899665544"
val number = phoneNumber.takeUnless { it.matches("[0-9]+".toRegex()) }

numbertendrá una cadena de phoneNumbersolo si no coincide con el regex. De lo contrario, lo será null.

Puede ver respuestas similares, lo cual es útil aquí, la diferencia entre kotlin también, aplicar, dejar, usar, tomarIf y tomar a menos que en Kotlin

Bhuvanesh BS
fuente
Tiene un error tipográfico en su último ejemplo, probablemente quiso decir en phoneNumber. takeUnless{}lugar de phoneNumber. takeIf{}.
Ryan Amaral
1
Corregido. Gracias @Ryan Amaral
Bhuvanesh BS
5

Hay 6 funciones de determinación del alcance diferentes:

  1. T.run
  2. T.let
  3. Aplicar
  4. T. también
  5. con
  6. correr

Preparé una nota visual como la siguiente para mostrar las diferencias:

data class Citizen(var name: String, var age: Int, var residence: String)

ingrese la descripción de la imagen aquí

La decisión depende de sus necesidades. Los casos de uso de diferentes funciones se superponen, por lo que puede elegir las funciones en función de las convenciones específicas utilizadas en su proyecto o equipo.

Aunque las funciones de alcance son una forma de hacer que el código sea más conciso, evite usarlas en exceso: puede disminuir la legibilidad de su código y provocar errores. Evite anidar funciones de alcance y tenga cuidado al encadenarlas: es fácil confundirse sobre el objeto de contexto actual y el valor de este o de él.

Aquí hay otro diagrama para decidir cuál usar de https://medium.com/@elye.project/mastering-kotlin-standard-functions-run-with-let-also-and-apply-9cd334b0ef84 ingrese la descripción de la imagen aquí

Algunas convenciones son las siguientes:

Úselo también para acciones adicionales que no alteren el objeto, como el registro o la impresión de información de depuración.

val numbers = mutableListOf("one", "two", "three")
 numbers
 .also { println("The list elements before adding new one: $it") }
 .add("four")

El caso común para aplicar es la configuración del objeto.

val adam = Person("Adam").apply {
age = 32
city = "London"        
}
println(adam)

Si necesita sombrear, use ejecutar

fun test() {
    var mood = "I am sad"

    run {
        val mood = "I am happy"
        println(mood) // I am happy
    }
    println(mood)  // I am sad
}

Si necesita devolver el objeto receptor en sí, use aplicar o también

oiyio
fuente
3

Según mi experiencia, dado que tales funciones son azúcar sintáctico en línea sin diferencia de rendimiento, siempre debe elegir la que requiera escribir la menor cantidad de código en lamda.

Para hacer esto, primero determine si desea que la lambda devuelva su resultado (elija run/ let) o el objeto en sí (elija apply/ also); luego, en la mayoría de los casos, cuando la lambda es una expresión única, elija las que tengan el mismo tipo de función de bloque que esa expresión, porque cuando es una expresión receptora, thisse puede omitir, cuando es una expresión de parámetro, ites más corta que 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"

Sin embargo, cuando la lambda consiste en una combinación de ellos, depende de usted elegir el que se ajuste mejor al contexto o con el que se sienta más cómodo.

Además, use los que tienen la función de bloque de parámetros cuando se necesite deconstrucción:

val pair: Pair<TypeA, TypeB> = ...

pair.run/*apply*/ {
    val (first, second) = this
    ...
} // longer
pair.let/*also*/ { (first, second) -> ... } // shorter

Aquí hay una breve comparación entre todas estas funciones del curso oficial de Kotlin de JetBrains en Coursera Kotlin para desarrolladores de Java : Tabla de diferencias Implementaciones simplificadas

Shreck Ye
fuente