O exemplo que você deu usa apenas chamada por valor, portanto, darei um novo exemplo mais simples que mostra a diferença.
Primeiro, vamos assumir que temos uma função com um efeito colateral. Esta função imprime algo e depois retorna um Int
.
def something() = {
println("calling something")
1 // return value
}
Agora vamos definir duas funções que aceitam Int
argumentos 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 ( something()
) 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. Em vez disso, as funções de chamada por nome recalculam o valor da expressão passada sempre que ela é acessada.
=> 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.f(2)
for compilado como uma expressão do tipoInt
, o código gerado chamaráf
com argumento2
e o resultado será o valor da expressão. Se esse mesmo texto for compilado como uma expressão do tipo=> Int
, o código gerado usará uma referência a algum tipo de "bloco de código" como o valor da expressão. De qualquer forma, um valor desse tipo pode ser passado para uma função que espera um parâmetro desse tipo. Tenho certeza de que você pode fazer isso com atribuição de variáveis, sem nenhum parâmetro passando à vista. Então, o que nomes ou chamadas têm algo a ver com isso?=> Int
é "função de nenhum argumento que gera um Int", como isso é diferente() => Int
? Scala parece tratar isso de maneira diferente, por exemplo,=> Int
aparentemente, não funciona como o tipo de aval
, apenas como o tipo de um parâmetro.=> Int
é uma conveniência e não é implementado exatamente como um objeto de função (presumivelmente por que você não pode ter variáveis do tipo=> Int
, embora não haja uma razão fundamental para que isso não funcione).() => Int
é explicitamente uma função de nenhum argumento que retornará umInt
, que precisa ser chamado explicitamente e pode ser passado como uma função.=> Int
é uma espécie de "proxyInt
", e a única coisa que você pode fazer com isso é chamá-lo (implicitamente) para obter oInt
.Aqui está um exemplo de Martin Odersky:
Queremos examinar a estratégia de avaliação e determinar qual é mais rápido (menos etapas) nestas condições:
chamada por valor: teste (2,3) -> 2 * 2 -> 4
chamada por nome: teste (2,3) -> 2 * 2 -> 4
Aqui o resultado é alcançado com o mesmo número de etapas.
chamada por valor: teste (7,8) -> 7 * 7 -> 49
chamada por nome: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
Aqui chamar pelo valor é mais rápido.
chamada por valor: teste (7,8) -> 7 * 7 -> 49
chamada por nome: 7 * 7 -> 49
Aqui a chamada por nome é mais rápida
chamada por valor: teste (7,2 * 4) -> teste (7, 8) -> 7 * 7 -> 49
chamada por nome: (3 + 4) (3 + 4) -> 7 (3 + 4) -> 7 * 7 -> 49
O resultado é alcançado nas mesmas etapas.
fonte
def test (x:Int, y: => Int) = x * x
observe que o parâmetro y nunca é usado.No caso do seu exemplo, todos os parâmetros serão avaliados antes de serem chamados na função, pois você os define apenas por valor . Se você deseja definir seus parâmetros pelo nome, deve passar um bloco de código:
Dessa forma, o parâmetro
x
não será avaliado até que seja chamado na função.Este pequeno post aqui explica isso muito bem.
fonte
Para repetir o argumento de @ Ben nos comentários acima, acho melhor pensar em "chamada por nome" como apenas açúcar sintático. O analisador apenas agrupa as expressões em funções anônimas, para que possam ser chamadas posteriormente, quando usadas.
Com efeito, em vez de definir
e executando:
Você também pode escrever:
E execute-o da seguinte maneira para o mesmo efeito:
fonte
=> T
e() => T
. Uma função que usa o primeiro tipo como parâmetro, não aceita o segundo, o scala armazena informações suficientes na@ScalaSignature
anotação para gerar um erro de tempo de compilação para isso. O bytecode para ambos=> T
e() => T
é o mesmo e é aFunction0
. Veja esta pergunta para mais detalhes.Vou tentar explicar por um caso de uso simples, em vez de apenas fornecer um exemplo
Imagine que você deseja criar um "aplicativo nagger" que o todas as vezes desde a última vez em que você foi incomodado.
Examine as seguintes implementações:
Na implementação acima, o nagger funcionará apenas ao passar pelo nome, o motivo é que, ao passar pelo valor, será reutilizado e, portanto, o valor não será reavaliado, enquanto ao passar pelo nome, o valor será reavaliado a cada vez que as variáveis são acessadas
fonte
Normalmente, parâmetros para funções são parâmetros por valor; isto é, o valor do parâmetro é determinado antes de ser passado para a função. Mas e se precisarmos escrever uma função que aceite como parâmetro uma expressão que não queremos que seja avaliada até que seja chamada dentro de nossa função? Para essa circunstância, o Scala oferece parâmetros de chamada por nome.
Um mecanismo de chamada por nome passa um bloco de código para o receptor e cada vez que o receptor acessa o parâmetro, o bloco de código é executado e o valor é calculado.
fonte
Como presumo, a
call-by-value
função conforme discutida acima passa apenas os valores para a função. De acordo comMartin Odersky
É uma estratégia de avaliação seguida por um Scala que desempenha um papel importante na avaliação de funções. Mas, simplifiquecall-by-name
. é como passar a função como argumento para o método também conhecido comoHigher-Order-Functions
. Quando o método acessa o valor do parâmetro passado, ele chama a implementação das funções passadas. como abaixo:De acordo com o exemplo @dhg, crie o método primeiro como:
Esta função contém uma
println
instrução e retorna um valor inteiro. Crie a função, que possui argumentos comocall-by-name
:Este parâmetro de função é definir uma função anônima que retorne um valor inteiro. Nele
x
contêm uma definição de função que0
passou argumentos, mas retornamint
valor e nossasomething
função contém a mesma assinatura. Quando chamamos a função, passamos a função como argumento paracallByName
. Mas no caso decall-by-value
apenas passar o valor inteiro para a função. Chamamos a função como abaixo:Nesse
something
método, nosso método é chamado duas vezes, porque quando acessamos o valor dex
incallByName
method, sua chamada para a definição desomething
method.fonte
Chamada por valor é um caso de uso geral, conforme explicado por muitas respostas aqui.
Vou tentar demonstrar a chamada pelo nome de maneira mais simples com os casos de uso abaixo
Exemplo 1:
Exemplo simples / caso de uso de chamada pelo nome está abaixo da função, que assume a função como parâmetro e fornece o tempo decorrido.
Exemplo 2:
O apache spark (com scala) usa o log usando chamada por nome, como vê a
Logging
característica na qual seu usuário avalia preguiçosamente se develog.isInfoEnabled
ou não a partir do método abaixo.fonte
Em uma chamada por valor , o valor da expressão é pré-calculado no momento da chamada da função e esse valor específico é passado como parâmetro para a função correspondente. O mesmo valor será usado em toda a função.
Enquanto em uma chamada por nome , a expressão em si é passada como parâmetro para a função e é computada apenas dentro da função, sempre que esse parâmetro específico é chamado.
A diferença entre Chamada por nome e Chamada por valor no Scala poderia ser melhor compreendida com o exemplo abaixo:
Fragmento de código
Resultado
No trecho de código acima, para a chamada de função CallbyValue (System.nanoTime ()) , o nano tempo do sistema é pré-calculado e esse valor pré-calculado passou um parâmetro para a chamada de função.
Mas na chamada da função CallbyName (System.nanoTime ()) , a própria expressão "System.nanoTime ())" é passada como um parâmetro para a chamada de função e o valor dessa expressão é calculado quando esse parâmetro é usado dentro da função .
Observe a definição da função CallbyName, onde existe um símbolo => separando o parâmetro x e seu tipo de dados. Esse símbolo específico indica que a função é chamada pelo tipo de nome.
Em outras palavras, os argumentos da função de chamada por valor são avaliados uma vez antes de inserir a função, mas os argumentos da função de chamada por nome são avaliados dentro da função somente quando são necessários.
Espero que isto ajude!
fonte
Aqui está um exemplo rápido que eu codifiquei para ajudar um colega meu que está atualmente fazendo o curso Scala. O que eu achei interessante é que Martin não usou a resposta da pergunta && apresentada anteriormente na palestra como exemplo. De qualquer forma, espero que isso ajude.
A saída do código será a seguinte:
fonte
Os parâmetros geralmente são passados por valor, o que significa que eles serão avaliados antes de serem substituídos no corpo da função.
Você pode forçar um parâmetro a ser chamado pelo nome usando a seta dupla ao definir a função.
fonte
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
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
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':
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
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á
Teste Caso1 (2,3)
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)
Nesse caso, a chamada por valor executa menos etapas
Teste Case3 (7, 2 * 4)
Evitamos o cálculo desnecessário do segundo argumento
Teste Case4 (3 + 4, 2 * 4)
Abordagem diferente
Primeiro, vamos assumir que temos uma função com um efeito colateral. Esta função imprime algo e retorna um Int.
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).
Agora, o que acontece quando os chamamos com nossa função de efeito colateral?
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:
E então usado assim:
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:
Imagine que você declararia o método && assim:
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 é:
fonte
Passar por um exemplo deve ajudá-lo a entender melhor a diferença.
Vamos definir uma função simples que retorne a hora atual:
Agora vamos definir uma função, por nome , que imprime duas vezes atrasada por um segundo:
E um por valor :
Agora vamos chamar cada um:
O resultado deve explicar a diferença. O trecho está disponível aqui .
fonte
CallByName
é invocado quando usado ecallByValue
invocado sempre que a instrução é encontrada.Por exemplo:-
Eu tenho um loop infinito, ou seja, se você executar esta função, nunca obteremos
scala
prompt.uma
callByName
função assume oloop
método acima como argumento e nunca é usada dentro de seu corpo.Na execução do
callByName
método, não encontramos nenhum problema (recebemos oscala
prompt de volta), pois não estamos usando a função loop dentro dacallByName
função.uma
callByValue
função assume oloop
método acima como parâmetro, como resultado da função ou expressão interna ser avaliada antes de executar a função externa porloop
função executada recursivamente e nunca recebemos oscala
prompt de volta.fonte
Veja isso:
y: => Int é chamada pelo nome. O que é passado como chamada pelo nome é add (2, 1). Isso será avaliado preguiçosamente. Portanto, a saída no console será "mul" seguida de "add", embora add pareça ser chamado primeiro. A chamada pelo nome funciona como um tipo de passagem de um ponteiro de função.
Agora mude de y: => Int para y: Int. O console mostrará "add" seguido de "mul"! Forma usual de avaliação.
fonte
Eu não acho que todas as respostas aqui fazem a justificação correta:
Na chamada por valor, os argumentos são calculados apenas uma vez:
Como você pode ver acima, todos os argumentos são avaliados se não forem necessários, normalmente
call-by-value
podem ser rápidos, mas nem sempre são como neste caso.Se a estratégia de avaliação fosse
call-by-name
, a decomposição teria sido:Como você pode ver acima, nunca precisamos avaliar
4 * 11
e, portanto, economizamos um pouco de computação que às vezes pode ser benéfico.fonte