Qual é a diferença entre "def" e "val" para definir uma função

214

Qual é a diferença entre:

def even: Int => Boolean = _ % 2 == 0

e

val even: Int => Boolean = _ % 2 == 0

Ambos podem ser chamados como even(10).

Amir Karimi
fonte
Oi, o que Int => Booleansignifica? Eu acho que a sintaxe de definição édef foo(bar: Baz): Bin = expr
Ziu
@Ziu significa que a função 'even' recebe um Int como argumento e retorna um Booleano como um tipo de valor. Assim, você pode chamar 'even (3)', que é avaliado como booleano 'false'
Denys Lobur
@ DenysLobur obrigado pela sua resposta! Alguma referência sobre esta sintaxe?
Ziu
@Ziu Basicamente, descobri isso no curso Cursera de Odersky - coursera.org/learn/progfun1 . Quando você terminar, você entenderá o que significa 'Type => Type' #
Denys Lobur 08/10

Respostas:

325

O método def evenavalia na chamada e cria novas funções sempre (nova instância de Function1).

def even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = false

val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true

Com defvocê pode obter novas funções em cada chamada:

val test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -1049057402
test()
// Int = -1049057402 - same result

def test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -240885810
test()
// Int = -1002157461 - new result

valavalia quando definido, def- quando chamado:

scala> val even: Int => Boolean = ???
scala.NotImplementedError: an implementation is missing

scala> def even: Int => Boolean = ???
even: Int => Boolean

scala> even
scala.NotImplementedError: an implementation is missing

Observe que há uma terceira opção: lazy val.

Ele avalia quando chamado pela primeira vez:

scala> lazy val even: Int => Boolean = ???
even: Int => Boolean = <lazy>

scala> even
scala.NotImplementedError: an implementation is missing

Mas retorna o mesmo resultado (neste caso, a mesma instância de FunctionN) sempre:

lazy val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true

lazy val test: () => Int = {
  val r = util.Random.nextInt
  () => r
}

test()
// Int = -1068569869
test()
// Int = -1068569869 - same result

atuação

val avalia quando definido.

defavalia em todas as chamadas, portanto, o desempenho pode ser pior do que valem várias chamadas. Você obterá o mesmo desempenho com uma única chamada. E sem chamadas, você não terá custos indiretos def, portanto, você pode defini-la mesmo que não a utilize em algumas ramificações.

Com um, lazy valvocê obtém uma avaliação preguiçosa: você pode defini-lo mesmo que não o use em alguns ramos, e ele avalia uma vez ou nunca, mas você terá um pouco de sobrecarga com o bloqueio de verificação dupla em todos os acessos ao seu lazy val.

Como o @SargeBorsch observou, você pode definir o método, e esta é a opção mais rápida:

def even(i: Int): Boolean = i % 2 == 0

Mas se você precisar de uma função (não método) para a composição da função ou para o filter(even)compilador de funções de ordem superior (como ), gerará uma função do seu método toda vez que você a estiver usando como função, portanto, o desempenho poderá ser um pouco pior do que com val.

senia
fonte
Você poderia compará-los em relação ao desempenho? Não é importante avaliar a função cada vez que evené chamada.
Amir Karimi
2
defpode ser usado para definir um método, e essa é a opção mais rápida. @ A.Karimi
Nome de exibição
2
Por diversão: em 2.12 even eq even,.
som-snytt
Existe um conceito de funções embutidas como no c ++? Eu estou vindo do mundo c ++, então perdoe minha ignorância.
Animageofmine
2
@animageofmine O compilador Scala pode tentar métodos embutidos. Há @inlineatributo para isso. Mas não pode integrar funções porque a chamada de função é uma chamada ao applymétodo virtual de um objeto de função. A JVM pode destirtualizar e alinhar essas chamadas em algumas situações, mas não em geral.
senia
24

Considere isto:

scala> def even: (Int => Boolean) = {
             println("def"); 
             (x => x % 2 == 0)
       }
even: Int => Boolean

scala> val even2: (Int => Boolean) = {
             println("val");
             (x => x % 2 == 0)
       }
val //gets printed while declaration. line-4
even2: Int => Boolean = <function1>

scala> even(1)
def
res9: Boolean = false

scala> even2(1)
res10: Boolean = false

Você vê a diferença? Em resumo:

def : Para cada chamada para even, ele chama o corpo do evenmétodo novamente. Mas com even2ie val , a função é inicializada apenas uma vez durante a declaração (e, portanto, imprime valna linha 4 e nunca mais) e a mesma saída é usada cada vez que é acessada. Por exemplo, tente fazer isso:

scala> import scala.util.Random
import scala.util.Random

scala> val x = { Random.nextInt }
x: Int = -1307706866

scala> x
res0: Int = -1307706866

scala> x
res1: Int = -1307706866

Quando xé inicializado, o valor retornado por Random.nextInté definido como o valor final de x. A próxima vez que xfor usada novamente, sempre retornará o mesmo valor.

Você também pode inicializar preguiçosamente x. ou seja, na primeira vez em que é usado, é inicializado e não durante a declaração. Por exemplo:

scala> lazy val y = { Random.nextInt }
y: Int = <lazy>

scala> y
res4: Int = 323930673

scala> y
res5: Int = 323930673
Jatin
fonte
6
Acho que sua explicação pode implicar algo que você não pretende. Tente ligar even2duas vezes, uma vez com 1e uma vez com 2. Você receberá respostas diferentes em cada chamada. Portanto, enquanto o printlnnão for executado em chamadas subsequentes, você não obtém o mesmo resultado de chamadas diferentes para even2. Quanto ao motivo de printlnnão ser executado novamente, essa é uma pergunta diferente.
17249 melston
1
isso é realmente muito interessante. É como no caso de val, ou seja, even2, a val é avaliada para um valor parametrizado. então sim com um val você a avaliação da função, seu valor. O println não faz parte do valor avaliado. Faz parte da avaliação, mas não o valor avaliado. O truque aqui é que o valor avaliado é realmente um valor parametarizado, que depende de alguma entrada. coisa inteligente
MaatDeamon
1
@melston exatamente! foi isso que eu entendi. Por que o println não é executado novamente enquanto a saída é alterada?
aur
1
@aur o que é retornado por even2 é na verdade uma função (a expressão entre parênteses no final da definição de even2). Essa função é realmente chamada com o parâmetro que você passa para even2 toda vez que a invoca.
melston
5

Veja isso:

  var x = 2 // using var as I need to change it to 3 later
  val sq = x*x // evaluates right now
  x = 3 // no effect! sq is already evaluated
  println(sq)

Surpreendentemente, isso imprimirá 4 e não 9! val (even var) é avaliado imediatamente e atribuído.
Agora mude val para def .. ele imprimirá 9! Def é uma chamada de função. Ele avalia cada vez que é chamado.

Apurva Singh
fonte
1

val ie "sq" é definido pela definição de Scala. É avaliado no momento da declaração, você não pode alterar mais tarde. Em outros exemplos, onde even2 também val, mas declarou com a assinatura da função ou seja "(Int => Boolean)", portanto não é do tipo Int. É uma função e seu valor é definido pela seguinte expressão

   {
         println("val");
         (x => x % 2 == 0)
   }

Conforme a propriedade Scala val, você não pode atribuir outra função para even2, mesma regra que sq.

Por que a função eval2 val não está imprimindo "val" repetidamente?

Código Orig:

val even2: (Int => Boolean) = {
             println("val");
             (x => x % 2 == 0)
       }

Sabemos que, em Scala, a última declaração do tipo de expressão acima (dentro de {..}) é realmente retornar ao lado esquerdo. Então você acaba configurando even2 para a função "x => x% 2 == 0", que corresponde ao tipo que você declarou para o tipo even2 val, isto é (Int => Boolean), para que o compilador fique feliz. Agora, even2 apenas aponta para a função "(x => x% 2 == 0)" (nenhuma outra declaração antes, por exemplo, println ("val")) etc. A chamada de event2 com parâmetros diferentes na verdade chama "(x => x% 2 == 0) ", pois somente isso é salvo com o evento2.

scala> even2(2)
res7: Boolean = true

scala> even2(3)
res8: Boolean = false

Apenas para esclarecer isso, a seguir é apresentada uma versão diferente do código.

scala> val even2: (Int => Boolean) = {
     |              println("val");
     |              (x => { 
     |               println("inside final fn")
     |               x % 2 == 0
     |             })
     |        }

O que vai acontecer ? aqui vemos "inside final fn" impresso repetidamente, quando você chama even2 ().

scala> even2(3)
inside final fn
res9: Boolean = false

scala> even2(2)
inside final fn
res10: Boolean = true

scala> 
Sandi
fonte
1

A execução de uma definição como def x = enão avaliará a expressão e. Em vez disso, é avaliado sempre que x é chamado.

Como alternativa, Scala oferece uma definição de valor val x = e, que avalia o lado direito como parte da avaliação da definição. Se x for usado posteriormente, será imediatamente substituído pelo valor pré-calculado de e, para que a expressão não precise ser avaliada novamente.

Gaurav Khare
fonte
0

Além disso, Val é uma avaliação por valor. O que significa que a expressão do lado direito é avaliada durante a definição. Onde Def é por avaliação de nome. Não avaliará até que seja usado.

Sandipan Ghosh
fonte
0

Além das respostas úteis acima, minhas descobertas são:

def test1: Int => Int = {
x => x
}
--test1: test1[] => Int => Int

def test2(): Int => Int = {
x => x+1
}
--test2: test2[]() => Int => Int

def test3(): Int = 4
--test3: test3[]() => Int

O exemplo acima mostra que “def” é um método (com zero parâmetros de argumento) que retorna outra função "Int => Int" quando invocada.

A conversão de métodos em funções é bem explicada aqui: https://tpolecat.github.io/2014/06/09/methods-functions.html

prateek
fonte
0

No REPL,

scala> def even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean

scala> val even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean = $$Lambda$1157/1017502292@57a0aeb8

def significa call-by-name, avaliado sob demanda

médias val call-by-value, avaliadas durante a inicialização

GraceMeng
fonte
Com uma pergunta tão antiga e com tantas respostas já enviadas, muitas vezes é útil explicar como sua resposta difere ou adiciona às informações fornecidas nas respostas existentes.
jwvh