El ejemplo que ha dado solo usa llamada por valor, por lo que daré un ejemplo nuevo y más simple que muestra la diferencia.
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 Int
argumentos 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
Por lo tanto, puede ver que en la versión de llamada por valor, el efecto secundario de la llamada de función pasada ( something()
) solo ocurrió 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. En cambio, las funciones de llamada por nombre vuelven a calcular el valor de la expresión pasada cada vez que se accede a ella.
=> 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.f(2)
se compila como una expresión de tipoInt
, el código generado llamaf
con argumento2
y el resultado es el valor de la expresión. Si ese mismo texto se compila como una expresión de tipo=> Int
, el código generado utiliza una referencia a algún tipo de "bloque de código" como el valor de la expresión. De cualquier manera, un valor de ese tipo se puede pasar a una función que espera un parámetro de ese tipo. Estoy bastante seguro de que puede hacer esto con asignación variable, sin pasar ningún parámetro a la vista. Entonces, ¿qué tienen que ver los nombres o las llamadas?=> Int
es "función de ningún argumento que genera un Int", ¿en qué se diferencia() => Int
? Scala parece tratarlos de manera diferente, por ejemplo,=> Int
aparentemente no funciona como el tipo de aval
, solo como el tipo de un parámetro.=> Int
es una conveniencia, y no se implementa exactamente como un objeto de función (presumiblemente por qué no puede tener variables de tipo=> Int
, aunque no hay una razón fundamental por la que esto no pueda funcionar).() => Int
es explícitamente una función sin argumentos que devolverá unInt
, que debe llamarse explícitamente y puede pasarse como una función.=> Int
es una especie de "proxyInt
", y lo único que puede hacer con él es llamarlo (implícitamente) para obtener elInt
.Aquí hay un ejemplo de Martin Odersky:
Queremos examinar la estrategia de evaluación y determinar cuál es más rápido (menos pasos) en estas condiciones:
llamada por valor: prueba (2,3) -> 2 * 2 -> 4
llamada por nombre: prueba (2,3) -> 2 * 2 -> 4
Aquí el resultado se alcanza con el mismo número de pasos.
llamada por valor: prueba (7,8) -> 7 * 7 -> 49
llamada por nombre: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Aquí llame Por valor es más rápido.
llamada por valor: prueba (7,8) -> 7 * 7 -> 49
llamada por nombre: 7 * 7 -> 49
Aquí la llamada por nombre es más rápida
llamada por valor: prueba (7,2 * 4) -> prueba (7, 8) -> 7 * 7 -> 49
llamada por nombre: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
El resultado se alcanza dentro de los mismos pasos.
fuente
def test (x:Int, y: => Int) = x * x
tenga en cuenta que el parámetro y nunca se usa.En el caso de su ejemplo, todos los parámetros se evaluarán antes de que se invoque en la función, ya que solo los está definiendo por valor . Si desea definir sus parámetros por nombre , debe pasar un bloque de código:
De esta manera, el parámetro
x
no se evaluará hasta que se llame en la función.Esta pequeña publicación aquí explica esto muy bien también.
fuente
Para repetir el punto de @Ben en los comentarios anteriores, creo que es mejor pensar en "llamar por nombre" como solo azúcar sintáctico. El analizador simplemente envuelve las expresiones en funciones anónimas, para que puedan llamarse en un momento posterior, cuando se usan.
En efecto, en lugar de definir
y corriendo:
También podrías escribir:
Y ejecútelo de la siguiente manera para obtener el mismo efecto:
fuente
=> T
y() => T
. Una función que toma el primer tipo como parámetro, no aceptará el segundo, scala almacena suficiente información en@ScalaSignature
anotaciones para generar un error de tiempo de compilación para esto. El código de bytes para ambos=> T
y() => T
es el mismo y aunque es unaFunction0
. Vea esta pregunta para más detalles.Trataré de explicarlo con un caso de uso simple en lugar de dar un ejemplo.
Imagina que quieres construir una "aplicación de nagger" que te molestará cada vez desde la última vez que te molestaron.
Examine las siguientes implementaciones:
En la implementación anterior, el nagger funcionará solo cuando pase por nombre, la razón es que, al pasar por valor, se reutilizará y, por lo tanto, el valor no se volverá a evaluar, mientras que al pasar por nombre, el valor se volverá a evaluar cada hora de acceso a las variables
fuente
Típicamente, los parámetros para las funciones son parámetros por valor; es decir, el valor del parámetro se determina antes de pasarlo a la función. Pero, ¿qué sucede si necesitamos escribir una función que acepte como parámetro una expresión que no queremos evaluar hasta que se llame dentro de nuestra función? Para esta circunstancia, Scala ofrece parámetros de llamada por nombre.
Un mecanismo de llamada por nombre pasa un bloque de código al destinatario y cada vez que el destinatario accede al parámetro, se ejecuta el bloque de código y se calcula el valor.
fuente
Como supongo, la
call-by-value
función como se discutió anteriormente pasa solo los valores a la función. De acuerdo conMartin Odersky
Es una estrategia de evaluación seguida de una escala que juega un papel importante en la evaluación de funciones. Pero, hazlo simplecall-by-name
. es como un pase de la función como argumento para el método también conocido comoHigher-Order-Functions
. Cuando el método accede al valor del parámetro pasado, llama a la implementación de las funciones pasadas. como a continuación:Según el ejemplo @dhg, cree el método primero como:
Esta función contiene una
println
declaración y devuelve un valor entero. Cree la función, que tiene argumentos como acall-by-name
:Este parámetro de función, define una función anónima que ha devuelto un valor entero. En este
x
contiene una definición de función que ha0
pasado argumentos pero devuelveint
valor y nuestrasomething
función contiene la misma firma. Cuando llamamos a la función, pasamos la función como argumento acallByName
. Pero en el caso de quecall-by-value
solo pase el valor entero a la función. Llamamos a la función de la siguiente manera:En esto, nuestro
something
método llamó dos veces, porque cuando accedemos al valor dex
incallByName
method, se llama a la definición delsomething
método.fuente
La llamada por valor es un caso de uso general como se explica en muchas respuestas aquí.
Trataré de demostrar la llamada por nombre de manera más simple con los casos de uso a continuación
Ejemplo 1:
Ejemplo simple / caso de uso de llamada por nombre está debajo de la función, que toma la función como parámetro y da el tiempo transcurrido.
Ejemplo 2
Apache chispa (con Scala) utiliza el registro usando la llamada por medio nombre ver
Logging
rasgo en la que sus evalúa perezosamente silog.isInfoEnabled
desde o no el siguiente método.fuente
En una Llamada por valor , el valor de la expresión se calcula previamente en el momento de la llamada a la función y ese valor particular se pasa como parámetro a la función correspondiente. Se utilizará el mismo valor en toda la función.
Mientras que en una llamada por nombre , la expresión en sí se pasa como un parámetro a la función y solo se calcula dentro de la función, siempre que se llame a ese parámetro en particular.
La diferencia entre Llamada por nombre y Llamada por valor en Scala podría entenderse mejor con el siguiente ejemplo:
Fragmento de código
Salida
En el fragmento de código anterior, para la función llamada CallbyValue (System.nanoTime ()) , el tiempo nano del sistema está precalculado y ese valor precalculado ha pasado un parámetro a la llamada a la función.
Pero en la llamada a la función CallbyName (System.nanoTime ()) , la expresión "System.nanoTime ())" en sí misma se pasa como un parámetro a la llamada a la función y el valor de esa expresión se calcula cuando ese parámetro se usa dentro de la función .
Observe la definición de función de la función CallbyName, donde hay un símbolo => que separa el parámetro x y su tipo de datos. Ese símbolo particular allí indica que la función es de llamar por tipo de nombre.
En otras palabras, los argumentos de la función llamar por valor se evalúan una vez antes de ingresar a la función, pero los argumentos de la función llamar por nombre se evalúan dentro de la función solo cuando son necesarios.
¡Espero que esto ayude!
fuente
Aquí hay un ejemplo rápido que codifiqué para ayudar a un colega mío que actualmente está tomando el curso Scala. Lo que pensé que era interesante es que Martin no utilizó la respuesta de preguntas && presentada anteriormente en la conferencia como ejemplo. En cualquier caso, espero que esto ayude.
El resultado del código será el siguiente:
fuente
Los parámetros generalmente pasan por valor, lo que significa que se evaluarán antes de ser sustituidos en el cuerpo de la función.
Puede forzar que un parámetro se llame por nombre utilizando la flecha doble al definir la función.
fuente
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í
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.
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':
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
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á
Prueba de caso 1 (2,3)
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)
En este caso, la llamada por valor realiza menos pasos
Prueba Case3 (7, 2 * 4)
Evitamos el cálculo innecesario del segundo argumento.
Prueba Case4 (3 + 4, 2 * 4)
Enfoque diferente
Primero, supongamos que tenemos una función con un efecto secundario. Esta función imprime algo y luego devuelve un Int.
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).
¿Qué sucede cuando los llamamos con nuestra función de efectos secundarios?
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:
Y luego se usa así:
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:
Imagina que declararías un método && como este:
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:
fuente
Pasar por un ejemplo debería ayudarlo a comprender mejor la diferencia.
Definamos una función simple que devuelve la hora actual:
Ahora definiremos una función, por nombre , que imprime dos veces retrasada por un segundo:
Y uno por valor :
Ahora llamemos a cada uno:
El resultado debería explicar la diferencia. El fragmento está disponible aquí .
fuente
CallByName
se invoca cuando se usa ycallByValue
se invoca cada vez que se encuentra la instrucción.Por ejemplo:-
Tengo un bucle infinito, es decir, si ejecuta esta función, nunca se nos
scala
solicitará.una
callByName
función toma elloop
método anterior como argumento y nunca se usa dentro de su cuerpo.En la ejecución del
callByName
método no encontramos ningún problema (recibimos unscala
aviso de regreso) ya que no estamos donde usar la función de bucle dentro de lacallByName
función.una
callByValue
función toma elloop
método anterior como un parámetro, ya que el resultado dentro de la función o expresión se evalúa antes de ejecutar la función externa allí mediante unaloop
función ejecutada recursivamente y nunca recibimos unscala
aviso.fuente
Mira esto:
y: => Int es llamada por nombre. Lo que se pasa como llamada por nombre es agregar (2, 1). Esto será evaluado perezosamente. Por lo tanto, la salida en la consola será "mul" seguida de "add", aunque add parece ser llamado primero. Llamar por nombre actúa como una especie de pasar un puntero de función.
Ahora cambie de y: => Int a y: Int. ¡La consola mostrará "agregar" seguido de "mul"! Forma habitual de evaluación.
fuente
No creo que todas las respuestas aquí hagan la justificación correcta:
En la llamada por valor, los argumentos se calculan solo una vez:
puede ver arriba que todos los argumentos se evalúan si los necesarios no lo son, normalmente
call-by-value
pueden ser rápidos pero no siempre como en este caso.Si la estrategia de evaluación fuera
call-by-name
entonces la descomposición habría sido:Como puede ver arriba, nunca tuvimos que evaluar
4 * 11
y, por lo tanto, guardamos un poco de cómputo, que a veces puede ser beneficioso.fuente