Já existem muitas respostas fantásticas para essa pergunta na Internet. Escreverei uma compilação de várias explicações e exemplos que reuni sobre o tópico, caso alguém ache útil
INTRODUÇÃO
chamada por valor (CBV)
Normalmente, parâmetros para funções são parâmetros de chamada por valor; ou seja, os parâmetros são avaliados da esquerda para a direita para determinar seu valor antes que a própria função seja avaliada
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
chamada por nome (CBN)
Mas e se precisarmos escrever uma função que aceite como parâmetro uma expressão que não avaliaremos até que seja chamada dentro de nossa função? Para essa circunstância, o Scala oferece parâmetros de chamada por nome. Significa que o parâmetro é passado para a função como está e sua avaliação ocorre após a substituição
def first1(a: Int, b: => Int): Int = a
first1(3 + 4, 5 + 6) // will be reduced to (3 + 4) and then to 7
Um mecanismo de chamada por nome passa um bloco de código para a chamada e cada vez que a chamada acessa o parâmetro, o bloco de código é executado e o valor é calculado. No exemplo a seguir, atrasado imprime uma mensagem demonstrando que o método foi inserido. Em seguida, atrasado imprime uma mensagem com seu valor. Finalmente, retornos atrasados '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)
}
}
No método atrasado
Obtendo tempo em nano segundos
Param: 2027245119786400
PRÓS E CONTRAS PARA CADA CASO
CBN:
+ Termina com mais frequência * verifique abaixo acima da terminação * + Tem a vantagem de um argumento de função não ser avaliado se o parâmetro correspondente não for usado na avaliação do corpo da função - É mais lento, cria mais classes (o que o programa leva mais tempo para carregar) e consome mais memória.
CBV:
+ Geralmente é exponencialmente mais eficiente que o CBN, porque evita essa recomputação repetida de argumentos que as expressões que chamam pelo nome envolvem. Ele avalia todos os argumentos de função apenas uma vez + É muito mais agradável com efeitos imperativos e efeitos colaterais, porque você tende a saber muito melhor quando expressões serão avaliadas. -Pode levar a um loop durante a avaliação dos parâmetros * verifique abaixo acima da terminação *
E se a rescisão não for garantida?
-Se a avaliação CBV de uma expressão e terminar, então a avaliação CBN de e também termina -A outra direção não é verdadeira
Exemplo de não rescisão
def first(x:Int, y:Int)=x
Considere a expressão primeiro (1, loop)
CBN: primeiro (1, loop) → 1 CBV: primeiro (1, loop) → reduz os argumentos dessa expressão. Como um é um loop, ele reduz argumentos infinitamente. Não termina
DIFERENÇAS EM CADA COMPORTAMENTO DE CASO
Vamos definir um teste de método 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
Teste Caso1 (2,3)
test(2,3) → 2*2 → 4
Como começamos com argumentos já avaliados, será a mesma quantidade de etapas para chamada por valor e chamada por nome
Teste 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
Nesse caso, a chamada por valor executa menos etapas
Teste Case3 (7, 2 * 4)
call-by-value: test(7, 2*4) → test(7,8) → 7 * 7 → 49
call-by-name: (7)*(7) → 49
Evitamos o cálculo desnecessário do segundo argumento
Teste 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
Abordagem diferente
Primeiro, vamos assumir que temos uma função com um efeito colateral. Esta função imprime algo e retorna um Int.
def something() = {
println("calling something")
1 // return value
}
Agora, vamos definir duas funções que aceitam argumentos Int exatamente iguais, exceto que uma recebe o argumento em um estilo de chamada por valor (x: Int) e a outra em um estilo de chamada por nome (x: => Int).
def callByValue(x: Int) = {
println("x1=" + x)
println("x2=" + x)
}
def callByName(x: => Int) = {
println("x1=" + x)
println("x2=" + x)
}
Agora, o que acontece quando os chamamos com nossa função de efeito colateral?
scala> callByValue(something())
calling something
x1=1
x2=1
scala> callByName(something())
calling something
x1=1
calling something
x2=1
Portanto, você pode ver que na versão de chamada por valor, o efeito colateral da chamada de função passada (algo ()) aconteceu apenas uma vez. No entanto, na versão chamada por nome, o efeito colateral aconteceu duas vezes.
Isso ocorre porque as funções de chamada por valor calculam o valor da expressão passada antes de chamar a função, portanto, o mesmo valor é acessado sempre. No entanto, as funções de chamada por nome recalculam o valor da expressão passada sempre que ela é acessada.
Exemplos onde é melhor usar a chamada por nome
De: https://stackoverflow.com/a/19036068/1773841
Exemplo simples de desempenho: log.
Vamos imaginar uma interface como esta:
trait Logger {
def info(msg: => String)
def warn(msg: => String)
def error(msg: => String)
}
E então usado assim:
logger.info("Time spent on X: " + computeTimeSpent)
Se o método info não fizer nada (porque, digamos, o nível de log foi configurado para mais alto que isso), o computeTimeSpent nunca será chamado, economizando tempo. Isso acontece muito com os criadores de logs, onde geralmente é possível manipular seqüências de caracteres que podem ser caras em relação às tarefas que estão sendo registradas.
Exemplo de correção: operadores lógicos.
Você provavelmente já viu código assim:
if (ref != null && ref.isSomething)
Imagine que você declararia o método && assim:
trait Boolean {
def &&(other: Boolean): Boolean
}
então, sempre que ref for nulo, você receberá um erro porque isSomething será chamado em uma referência nula antes de ser passado para &&. Por esse motivo, a declaração real é:
trait Boolean {
def &&(other: => Boolean): Boolean =
if (this) this else other
}
=> Int
é um tipo diferente deInt
; é "função de nenhum argumento que irá gerar umInt
" vs justInt
. Depois de obter funções de primeira classe, você não precisa inventar a terminologia de chamada por nome para descrever isso.