Qual è la differenza tra “def” e “val” per definire una funzione


214

Qual è la differenza tra:

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

e

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

Entrambi possono essere chiamati come even(10).


Ciao cosa Int => Booleansignifica? Penso che la sintassi di definizione siadef foo(bar: Baz): Bin = expr
Ziu,

@Ziu significa che la funzione 'pari' riceve un Int come argomento e restituisce un valore booleano come tipo di valore. Quindi puoi chiamare 'even (3)' che valuta Boolean 'false'
Denys Lobur

@DenysLobur grazie per la tua risposta! Qualche riferimento su questa sintassi?
Ziu,

@Ziu In pratica l'ho scoperto dal corso Coursera di Odersky - coursera.org/learn/progfun1 . Quando lo finirai, capirai cosa significa 'Tipo => Tipo'
Denys Lobur

Risposte:


325

Il metodo def evenvaluta alla chiamata e crea ogni volta una nuova funzione (nuova istanza di Function1).

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

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

Con defte puoi ottenere una nuova funzione ad ogni chiamata:

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

valvaluta quando definito, def- quando chiamato:

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

Si noti che c'è una terza opzione: lazy val.

Valuta quando viene chiamato la prima volta:

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

scala> even
scala.NotImplementedError: an implementation is missing

Restituisce sempre lo stesso risultato (in questo caso la stessa istanza di FunctionN) ogni volta:

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

Prestazione

val valuta quando definito.

defvaluta su ogni chiamata, quindi le prestazioni potrebbero essere peggiori rispetto vala più chiamate. Otterrai le stesse prestazioni con una sola chiamata. E senza chiamate non avrai costi generali def, quindi puoi definirlo anche se non lo userai in alcuni rami.

Con a lazy valotterrai una valutazione pigra: puoi definirla anche se non la userai in alcuni rami, e la valutazione una volta o mai più, ma otterrai un piccolo overhead dal doppio controllo del blocco su ogni accesso al tuo lazy val.

Come notato da @SargeBorsch, potresti definire il metodo, e questa è l'opzione più veloce:

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

Ma se hai bisogno di una funzione (non metodo) per la composizione della funzione o per funzioni di ordine superiore (come filter(even)) il compilatore genererà una funzione dal tuo metodo ogni volta che la usi come funzione, quindi le prestazioni potrebbero essere leggermente peggiori rispetto a val.


Li confronteresti per quanto riguarda le prestazioni? Non è importante valutare la funzione ogni volta che evenviene chiamato.
Amir Karimi,

2
defpuò essere usato per definire un metodo, e questa è l'opzione più veloce. @ A.Karimi
Visualizza nome

2
Per il divertimento: il 2.12, even eq even.
som-snytt,

Esiste un concetto di funzioni inline come in c ++? Vengo dal mondo c ++, quindi scusate la mia ignoranza.
animageofmine,

2
Il compilatore Scala @animageofmine può provare a incorporare metodi. C'è un @inlineattributo per questo. Ma non può incorporare le funzioni perché la chiamata di funzione è una chiamata al applymetodo virtuale di un oggetto funzione. JVM potrebbe devirtualizzare e incorporare tali chiamate in alcune situazioni, ma non in generale.
senia,

24

Considera questo:

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

Vedi la differenza? In breve:

def : per ogni chiamata a even, chiama evennuovamente il corpo del metodo. Ma con even2ie val , la funzione viene inizializzata solo una volta durante la dichiarazione (e quindi stampa valalla riga 4 e mai più) e lo stesso output viene utilizzato ogni volta che accede. Ad esempio, prova a fare questo:

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 xviene inizializzato, il valore restituito da Random.nextIntviene impostato come valore finale di x. La prossima volta che xviene usato di nuovo, restituirà sempre lo stesso valore.

Puoi anche inizializzare pigramente x. cioè la prima volta che viene usato viene inizializzato e non durante la dichiarazione. Per esempio:

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

scala> y
res4: Int = 323930673

scala> y
res5: Int = 323930673

6
Penso che la tua spiegazione possa implicare qualcosa che non intendi. Prova a chiamare even2due volte, una volta con 1e una volta con 2. Riceverai risposte diverse in ogni chiamata. Quindi, mentre printlnnon viene eseguito nelle chiamate successive, non si ottiene lo stesso risultato da chiamate diverse a even2. Per quanto riguarda il motivo per cui printlnnon viene eseguito di nuovo, questa è una domanda diversa.
Melston,

1
quello è in realtà molto interessante. È come nel caso di val, ovvero even2, la val viene valutata su un valore parametrizzato. quindi sì con una valutazione della funzione, del suo valore. Println non fa parte del valore valutato. Fa parte della valutazione ma non del valore valutato. Il trucco qui è che il valore valutato è in realtà un valore parametarizzato, che dipende da alcuni input. cosa intelligente
MaatDeamon,

1
@melston esattamente! questo è quello che ho capito, quindi perché Println non viene eseguito di nuovo mentre l'output è cambiato?
aur,

1
@aur ciò che viene restituito da even2 è in realtà una funzione (l'espressione tra parentesi alla fine della definizione di even2). Quella funzione viene effettivamente chiamata con il parametro che passi a even2 ogni volta che la invochi.
Melston,

5

Guarda questo:

  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)

Sorprendentemente, questo stamperà 4 e non 9! val (anche var) viene valutato immediatamente e assegnato.
Ora cambia val in def .. stamperà 9! Def è una chiamata di funzione .. verrà valutata ogni volta che viene chiamata.


1

val ie "sq" è da Scala la definizione è fissa. Viene valutato proprio al momento della dichiarazione, non è possibile modificarlo in seguito. In altri esempi, in cui even2 vale anche, ma ha dichiarato con la firma della funzione ovvero "(Int => Boolean)", quindi non è di tipo Int. È una funzione e il suo valore è impostato seguendo l'espressione

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

Come per la proprietà Scala val, non è possibile assegnare un'altra funzione a even2, stessa regola di sq.

Perché non chiamare ripetutamente la funzione val di eval2 val?

Codice orig:

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

Sappiamo che in Scala l'ultima affermazione del precedente tipo di espressione (dentro {..}) è in realtà tornare alla sinistra. Quindi finisci per impostare even2 su "x => x% 2 == 0", che corrisponde al tipo che hai dichiarato per even2 val type ie (Int => Boolean), quindi il compilatore è contento. Ora even2 punta solo alla funzione "(x => x% 2 == 0)" (non qualsiasi altra istruzione prima di es. Println ("val") ecc. Invocare event2 con parametri diversi in realtà invocherà "(x => x% 2 == 0) "codice, poiché solo quello viene salvato con event2.

scala> even2(2)
res7: Boolean = true

scala> even2(3)
res8: Boolean = false

Giusto per chiarire questo, di seguito è riportata una versione diversa del codice.

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

Cosa accadrà ? qui vediamo "inside final fn" stampato ancora e ancora, quando chiami even2 ().

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

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

scala> 

1

L'esecuzione di una definizione come def x = enon valuterà l'espressione e. Invece e viene valutato ogni volta che viene invocato x.

In alternativa, Scala offre una definizione di valore val x = e, che valuta il lato destro come parte della valutazione della definizione. Se x viene quindi utilizzato successivamente, viene immediatamente sostituito dal valore pre-calcolato di e, quindi l'espressione non deve essere valutata di nuovo.


0

inoltre, Val è una valutazione in base al valore. Ciò significa che l'espressione sul lato destro viene valutata durante la definizione. Dove Def è per valutazione del nome. Non valuterà fino a quando non verrà utilizzato.


0

Oltre alle risposte utili di cui sopra, i miei risultati sono:

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

Quanto sopra mostra che "def" è un metodo (con parametri argomento zero) che restituisce un'altra funzione "Int => Int" quando viene invocato.

La conversione dei metodi in funzioni è ben spiegata qui: https://tpolecat.github.io/2014/06/09/methods-functions.html


0

In 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, valutato su richiesta

val significa call-by-value, valutato durante l'inizializzazione


Con una domanda così vecchia e con così tante risposte già presentate, è spesso utile spiegare in che modo la tua risposta differisce o si aggiunge alle informazioni fornite nelle risposte esistenti.
jwvh,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.