Ya hay muchas respuestas fantásticas para esta pregunta en Internet. Escribiré una compilación de varias explicaciones y ejemplos que he reunido sobre el tema, en caso de que alguien pueda encontrarlo útil.
INTRODUCCIÓN
llamada por valor (CBV)
Típicamente, los parámetros para las funciones son parámetros de llamada por valor; es decir, los parámetros se evalúan de izquierda a derecha para determinar su valor antes de evaluar la función en sí
def first(a: Int, b: Int): Int = a
first(3 + 4, 5 + 6) // will be reduced to first(7, 5 + 6), then first(7, 11), and then 7
llamada por nombre (CBN)
Pero, ¿qué sucede si necesitamos escribir una función que acepte como parámetro una expresión que no evaluaremos hasta que se llame dentro de nuestra función? Para esta circunstancia, Scala ofrece parámetros de llamada por nombre. Es decir, el parámetro se pasa a la función tal como es, y su valoración se lleva a cabo después de la sustitución.
def first1(a: Int, b: => Int): Int = a
first1(3 + 4, 5 + 6) // will be reduced to (3 + 4) and then to 7
Un mecanismo de llamada por nombre pasa un bloque de código a la llamada y cada vez que la llamada accede al parámetro, se ejecuta el bloque de código y se calcula el valor. En el siguiente ejemplo, retrasado imprime un mensaje que demuestra que se ha ingresado el método. A continuación, retrasado imprime un mensaje con su valor. Finalmente, los retornos retrasados 't':
object Demo {
def main(args: Array[String]) {
delayed(time());
}
def time() = {
println("Getting time in nano seconds")
System.nanoTime
}
def delayed( t: => Long ) = {
println("In delayed method")
println("Param: " + t)
}
}
En método retrasado
Obtención de tiempo en nano segundos
Parám .: 2027245119786400
PROS Y CONTRAS POR CADA CASO
CBN:
+ Termina más a menudo * verifique debajo de la terminación anterior * + Tiene la ventaja de que un argumento de función no se evalúa si el parámetro correspondiente no se utiliza en la evaluación del cuerpo de la función -Es más lento, crea más clases (lo que significa que el programa toma más tiempo para cargar) y consume más memoria.
CBV:
+ A menudo es exponencialmente más eficiente que CBN, porque evita esta recalculación repetida de expresiones de argumentos que conlleva llamar por nombre. Evalúa cada argumento de función solo una vez + Juega mucho mejor con efectos imperativos y efectos secundarios, porque tiendes a saber mucho mejor cuándo se evaluarán las expresiones. -Puede provocar un bucle durante la evaluación de sus parámetros * verifique debajo de la terminación anterior *
¿Qué pasa si no se garantiza la terminación?
-Si la evaluación CBV de una expresión e termina, entonces la evaluación CBN de e también termina -La otra dirección no es verdadera
Ejemplo de no terminación
def first(x:Int, y:Int)=x
Considere primero la expresión (1, bucle)
CBN: primero (1, bucle) → 1 CBV: primero (1, bucle) → reduce los argumentos de esta expresión. Como uno es un bucle, reduce los argumentos infinitamente. No termina
DIFERENCIAS EN CADA CASO DE COMPORTAMIENTO
Definamos un método de prueba que será
Def test(x:Int, y:Int) = x * x //for call-by-value
Def test(x: => Int, y: => Int) = x * x //for call-by-name
Prueba de caso 1 (2,3)
test(2,3) → 2*2 → 4
Como comenzamos con argumentos ya evaluados, será la misma cantidad de pasos para call-by-value y call-by-name
Prueba Case2 (3 + 4,8)
call-by-value: test(3+4,8) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7 * (3+4) → 7 * 7 → 49
En este caso, la llamada por valor realiza menos pasos
Prueba Case3 (7, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (7)*(7) → 49
Evitamos el cálculo innecesario del segundo argumento.
Prueba Case4 (3 + 4, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (3+4)*(3+4) → 7*(3+4) → 7*7 → 49
Enfoque diferente
Primero, supongamos que tenemos una función con un efecto secundario. Esta función imprime algo y luego devuelve un Int.
def something() = {
println("calling something")
1 // return value
}
Ahora vamos a definir dos funciones que aceptan argumentos Int que son exactamente iguales, excepto que uno toma el argumento en un estilo de llamada por valor (x: Int) y el otro en un estilo de llamada por nombre (x: => Int).
def callByValue(x: Int) = {
println("x1=" + x)
println("x2=" + x)
}
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
¿Qué sucede cuando los llamamos con nuestra función de efectos secundarios?
scala> callByValue(something())
calling something
x1=1
x2=1
scala> callByName(something())
calling something
x1=1
calling something
x2=1
Entonces puede ver que en la versión de llamada por valor, el efecto secundario de la llamada de función pasada (algo ()) solo sucedió una vez. Sin embargo, en la versión llamada por nombre, el efecto secundario ocurrió dos veces.
Esto se debe a que las funciones de llamada por valor calculan el valor de la expresión pasada antes de llamar a la función, por lo tanto, se accede al mismo valor cada vez. Sin embargo, las funciones de llamada por nombre vuelven a calcular el valor de la expresión transferida cada vez que se accede a ella.
EJEMPLOS DONDE ES MEJOR USAR LLAMADA POR NOMBRE
Desde: https://stackoverflow.com/a/19036068/1773841
Ejemplo de rendimiento simple: registro.
Imaginemos una interfaz como esta:
trait Logger {
def info(msg: => String)
def warn(msg: => String)
def error(msg: => String)
}
Y luego se usa así:
logger.info("Time spent on X: " + computeTimeSpent)
Si el método de información no hace nada (porque, por ejemplo, el nivel de registro se configuró para un nivel superior), entonces nunca se llama a computeTimeSpent, lo que ahorra tiempo. Esto sucede mucho con los registradores, donde a menudo se ve una manipulación de cadenas que puede ser costosa en relación con las tareas que se registran.
Ejemplo de corrección: operadores lógicos.
Probablemente hayas visto un código como este:
if (ref != null && ref.isSomething)
Imagina que declararías un método && como este:
trait Boolean {
def &&(other: Boolean): Boolean
}
entonces, siempre que ref sea nulo, obtendrá un error porque isSomething se llamará con una referencia nula antes de pasar a &&. Por esta razón, la declaración real es:
trait Boolean {
def &&(other: => Boolean): Boolean =
if (this) this else other
}
=> Int
es un tipo diferente deInt
; es "función de ningún argumento que generará unInt
" vs justoInt
. Una vez que tenga funciones de primera clase, no necesita inventar terminología de llamada por nombre para describir esto.