Llamada por nombre vs llamada por valor en Scala, se necesita aclaración


239

Según tengo entendido, en Scala, una función puede llamarse

  • por valor o
  • por nombre

Por ejemplo, dadas las siguientes declaraciones, ¿sabemos cómo se llamará a la función?

Declaración:

def  f (x:Int, y:Int) = x;

Llamada

f (1,2)
f (23+55,5)
f (12+3, 44*11)

¿Cuáles son las reglas por favor?

Respuestas:


540

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 Intargumentos 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.


296
Siempre he pensado que esta terminología es innecesariamente confusa. Una función puede tener múltiples parámetros que varían en su estado de llamada por nombre frente a estado de llamada por valor. Entonces, no es que una función sea ​​llamada por nombre o llamada por valor, es que cada uno de sus parámetros puede ser pasar por nombre o pasar por valor. Además, "llamar por nombre" no tiene nada que ver con los nombres . => Intes un tipo diferente de Int; es "función de ningún argumento que generará un Int" vs justo Int. Una vez que tenga funciones de primera clase, no necesita inventar terminología de llamada por nombre para describir esto.
Ben

2
@Ben, eso ayuda a responder un par de preguntas, gracias. Desearía que más informes explicaran la semántica de pasar por nombre esto claramente.
Christopher Poile

3
@SelimOber Si el texto f(2)se compila como una expresión de tipo Int, el código generado llama fcon argumento 2y 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?
Ben

44
@Ben Entonces, si => Intes "función de ningún argumento que genera un Int", ¿en qué se diferencia () => Int? Scala parece tratarlos de manera diferente, por ejemplo, => Intaparentemente no funciona como el tipo de a val, solo como el tipo de un parámetro.
Tim Goodman

55
@TimGoodman Tienes razón, es un poco más complicado de lo que imaginé. => Intes 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). () => Intes explícitamente una función sin argumentos que devolverá un Int, que debe llamarse explícitamente y puede pasarse como una función. => Intes una especie de "proxy Int", y lo único que puede hacer con él es llamarlo (implícitamente) para obtener el Int.
Ben

51

Aquí hay un ejemplo de Martin Odersky:

def test (x:Int, y: Int)= x*x

Queremos examinar la estrategia de evaluación y determinar cuál es más rápido (menos pasos) en estas condiciones:

test (2,3)

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.

test (3+4,8)

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.

test (7,2*4)

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

test (3+4, 2*4) 

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.


1
En el tercer ejemplo para CBV, creo que quisiste decir prueba (7,8) en lugar de prueba (7,14)
talonx

1
El ejemplo está tomado de Coursera, principio en la programación scala. Lección 1.2. La llamada por nombre debe leer, def test (x:Int, y: => Int) = x * xtenga en cuenta que el parámetro y nunca se usa.
Dr. Jerry

1
¡Buen ejemplo! Tomado del Coursera MOOC :)
alxsimo

Esta es una buena explicación de la diferencia, pero no pidió la dirección a ser cuestión, a saber cuál de los dos está llamando Scala
db1234

16

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:

def f(x: => Int, y:Int) = x

De esta manera, el parámetro xno se evaluará hasta que se llame en la función.

Esta pequeña publicación aquí explica esto muy bien también.


10

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

def callByName(x: => Int) = {
  println("x1=" + x)
  println("x2=" + x)
}

y corriendo:

scala> callByName(something())
calling something
x1=1
calling something
x2=1

También podrías escribir:

def callAlsoByName(x: () => Int) = {
  println("x1=" + x())
  println("x2=" + x())
}

Y ejecútelo de la siguiente manera para obtener el mismo efecto:

callAlsoByName(() => {something()})

calling something
x1=1
calling something
x2=1

Creo que quiso decir: <! - idioma: lang-scala -> def callAlsoByName (x: () => Int) = {println ("x1 =" + x ()) println ("x2 =" + x ( ))} y luego: <! - idioma: lang-js -> callAlsoByName (() => something ()) No creo que necesites las llaves alrededor de algo () en esta última llamada. Nota: Intenté editar su respuesta, pero los revisores rechazaron mi edición diciendo que debería ser un comentario o una respuesta separada.
lambdista

Aparentemente no puedes usar el resaltado de sintaxis en los comentarios, ¡así que ignora la parte "<! - language: lang-scala ->"! Habría editado mi propio comentario, ¡pero solo puedes hacerlo en 5 minutos! :)
lambdista

1
Recientemente me encontré con esto también. Está bien conceptualmente pensarlo así, pero la escala diferencia entre => Ty () => T. Una función que toma el primer tipo como parámetro, no aceptará el segundo, scala almacena suficiente información en @ScalaSignatureanotaciones para generar un error de tiempo de compilación para esto. El código de bytes para ambos => Ty () => Tes el mismo y aunque es una Function0. Vea esta pregunta para más detalles.
vsnyc

6

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:

object main  {

    def main(args: Array[String]) {

        def onTime(time: Long) {
            while(time != time) println("Time to Nag!")
            println("no nags for you!")
        }

        def onRealtime(time: => Long) {
            while(time != time) println("Realtime Nagging executed!")
        }

        onTime(System.nanoTime())
        onRealtime(System.nanoTime())
    }
}

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


4

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.

object Test {
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)
  t
}
}
 1. C: /> scalac Test.scala 
 2. Prueba de escala
 3. En método retrasado
 4. Obtener tiempo en nano segundos
 5. Param: 81303808765843
 6. Obtener tiempo en nano segundos

2

Como supongo, la call-by-valuefunción como se discutió anteriormente pasa solo los valores a la función. De acuerdo con Martin OderskyEs una estrategia de evaluación seguida de una escala que juega un papel importante en la evaluación de funciones. Pero, hazlo simple call-by-name. es como un pase de la función como argumento para el método también conocido como Higher-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:

def something() = {
 println("calling something")
 1 // return value
}  

Esta función contiene una printlndeclaración y devuelve un valor entero. Cree la función, que tiene argumentos como a call-by-name:

def callByName(x: => Int) = {
 println("x1=" + x)
 println("x2=" + x)
}

Este parámetro de función, define una función anónima que ha devuelto un valor entero. En este xcontiene una definición de función que ha 0pasado argumentos pero devuelve intvalor y nuestra somethingfunción contiene la misma firma. Cuando llamamos a la función, pasamos la función como argumento a callByName. Pero en el caso de que call-by-valuesolo pase el valor entero a la función. Llamamos a la función de la siguiente manera:

scala> callByName(something())
 calling something
 x1=1
 calling something
 x2=1 

En esto, nuestro somethingmétodo llamó dos veces, porque cuando accedemos al valor de xin callByNamemethod, se llama a la definición del somethingmétodo.


2

La llamada por valor es un caso de uso general como se explica en muchas respuestas aquí.

Call-by-name pasa un bloque de código a la persona que llama y cada vez que la persona accede al parámetro, se ejecuta el bloque de código y se calcula el valor.

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.

 /**
   * Executes some code block and prints to stdout the 
time taken to execute   the block 
for interactive testing and debugging.
   */
  def time[T](f: => T): T = {
    val start = System.nanoTime()
    val ret = f
    val end = System.nanoTime()

    println(s"Time taken: ${(end - start) / 1000 / 1000} ms")

    ret
  }

Ejemplo 2

Apache chispa (con Scala) utiliza el registro usando la llamada por medio nombre ver Loggingrasgo en la que sus evalúa perezosamente si log.isInfoEnableddesde o no el siguiente método.

protected def logInfo(msg: => String) {
     if (log.isInfoEnabled) log.info(msg)
 }

2

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

object CallbyExample extends App {

  // function definition of call by value
  def CallbyValue(x: Long): Unit = {
    println("The current system time via CBV: " + x);
    println("The current system time via CBV " + x);
  }

  // function definition of call by name
  def CallbyName(x: => Long): Unit = {
    println("The current system time via CBN: " + x);
    println("The current system time via CBN: " + x);
  }

  // function call
  CallbyValue(System.nanoTime());
  println("\n")
  CallbyName(System.nanoTime());
}

Salida

The current system time via CBV: 1153969332591521
The current system time via CBV 1153969332591521


The current system time via CBN: 1153969336749571
The current system time via CBN: 1153969336856589

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!


2

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.

val start = Instant.now().toEpochMilli

val calc = (x: Boolean) => {
    Thread.sleep(3000)
    x
}


def callByValue(x: Boolean, y: Boolean): Boolean = {
    if (!x) x else y
}

def callByName(x: Boolean, y: => Boolean): Boolean = {
    if (!x) x else y
}

new Thread(() => {
    println("========================")
    println("Call by Value " + callByValue(false, calc(true)))
    println("Time " + (Instant.now().toEpochMilli - start) + "ms")
    println("========================")
}).start()


new Thread(() => {
    println("========================")
    println("Call by Name " + callByName(false, calc(true)))
    println("Time " + (Instant.now().toEpochMilli - start) + "ms")
    println("========================")
}).start()


Thread.sleep(5000)

El resultado del código será el siguiente:

========================
Call by Name false
Time 64ms
========================
Call by Value false
Time 3068ms
========================

1

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.

// first parameter will be call by value, second call by name, using `=>`
def returnOne(x: Int, y: => Int): Int = 1

// to demonstrate the benefits of call by name, create an infinite recursion
def loop(x: Int): Int = loop(x)

// will return one, since `loop(2)` is passed by name so no evaluated
returnOne(2, loop(2))

// will not terminate, since loop(2) will evaluate. 
returnOne(loop(2), 2) // -> returnOne(loop(2), 2) -> returnOne(loop(2), 2) -> ... 

1

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*24

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 * 749
call-by-name: (3+4)*(3+4)7 * (3+4)7 * 749

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 * 749
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 * 749
call-by-name: (3+4)*(3+4)7*(3+4)7*749

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
}

1

Pasar por un ejemplo debería ayudarlo a comprender mejor la diferencia.

Definamos una función simple que devuelve la hora actual:

def getTime = System.currentTimeMillis

Ahora definiremos una función, por nombre , que imprime dos veces retrasada por un segundo:

def getTimeByName(f: => Long) = { println(f); Thread.sleep(1000); println(f)}

Y uno por valor :

def getTimeByValue(f: Long) = { println(f); Thread.sleep(1000); println(f)}

Ahora llamemos a cada uno:

getTimeByName(getTime)
// prints:
// 1514451008323
// 1514451009325

getTimeByValue(getTime)
// prints:
// 1514451024846
// 1514451024846

El resultado debería explicar la diferencia. El fragmento está disponible aquí .


0

CallByNamese invoca cuando se usa y callByValuese 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 scalasolicitará.

scala> def loop(x:Int) :Int = loop(x-1)
loop: (x: Int)Int

una callByNamefunción toma el loopmétodo anterior como argumento y nunca se usa dentro de su cuerpo.

scala> def callByName(x:Int,y: => Int)=x
callByName: (x: Int, y: => Int)Int

En la ejecución del callByNamemétodo no encontramos ningún problema (recibimos un scalaaviso de regreso) ya que no estamos donde usar la función de bucle dentro de la callByNamefunción.

scala> callByName(1,loop(10))
res1: Int = 1
scala> 

una callByValuefunción toma el loopmé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 una loopfunción ejecutada recursivamente y nunca recibimos un scalaaviso.

scala> def callByValue(x:Int,y:Int) = x
callByValue: (x: Int, y: Int)Int

scala> callByValue(1,loop(1))

0

Mira esto:

    object NameVsVal extends App {

  def mul(x: Int, y: => Int) : Int = {
    println("mul")
    x * y
  }
  def add(x: Int, y: Int): Int = {
    println("add")
    x + y
  }
  println(mul(3, add(2, 1)))
}

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.


-2

No creo que todas las respuestas aquí hagan la justificación correcta:

En la llamada por valor, los argumentos se calculan solo una vez:

def f(x : Int, y :Int) = x

// following the substitution model

f(12 + 3, 4 * 11)
f(15, 4194304)
15

puede ver arriba que todos los argumentos se evalúan si los necesarios no lo son, normalmente call-by-valuepueden ser rápidos pero no siempre como en este caso.

Si la estrategia de evaluación fuera call-by-nameentonces la descomposición habría sido:

f(12 + 3, 4 * 11)
12 + 3
15

Como puede ver arriba, nunca tuvimos que evaluar 4 * 11y, por lo tanto, guardamos un poco de cómputo, que a veces puede ser beneficioso.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.