Was ist der Unterschied zwischen "def" und "val", um eine Funktion zu definieren


214

Was ist der Unterschied zwischen:

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

und

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

Beide können wie genannt werden even(10).


Hallo, was heißt Int => Booleandas? Ich denke, die definierte Syntax istdef foo(bar: Baz): Bin = expr
Ziu

@Ziu bedeutet, dass die Funktion 'gerade' ein Int als Argument empfängt und einen Booleschen Wert als Werttyp zurückgibt. Sie können also 'gerade (3)' aufrufen, was zu Booleschem
Wert

@DenysLobur danke für deine Antwort! Irgendwelche Hinweise zu dieser Syntax?
Ziu

@Ziu Ich habe es im Grunde aus Oderskys Coursera-Kurs herausgefunden - coursera.org/learn/progfun1 . Wenn Sie fertig sind, werden Sie verstehen, was 'Typ => Typ' bedeutet
Denys Lobur

Antworten:


325

Die Methode wird def evenbeim Aufruf ausgewertet und erstellt jedes Mal eine neue Funktion (neue Instanz von Function1).

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

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

Mit können defSie bei jedem Anruf eine neue Funktion erhalten:

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

valwertet aus, wenn definiert, def- wenn aufgerufen:

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

Beachten Sie, dass es eine dritte Option gibt : lazy val.

Es wird beim ersten Aufruf ausgewertet:

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

scala> even
scala.NotImplementedError: an implementation is missing

Gibt aber FunctionNjedes Mal das gleiche Ergebnis (in diesem Fall die gleiche Instanz von ) zurück:

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

Performance

val wird ausgewertet, wenn definiert.

defWird bei jedem Anruf ausgewertet, sodass die Leistung möglicherweise schlechter ist als valbei mehreren Anrufen. Mit einem einzigen Anruf erhalten Sie die gleiche Leistung. Und ohne Anrufe erhalten Sie keinen Overhead def, sodass Sie ihn definieren können, auch wenn Sie ihn in einigen Filialen nicht verwenden.

Mit a erhalten lazy valSie eine verzögerte Bewertung: Sie können sie definieren, auch wenn Sie sie in einigen Zweigen nicht verwenden, und sie wird einmal oder nie ausgewertet, aber Sie erhalten einen kleinen Overhead, wenn Sie bei jedem Zugriff auf Ihre Funktion die doppelte Überprüfung sperren lazy val.

Wie @SargeBorsch feststellte, können Sie eine Methode definieren, und dies ist die schnellste Option:

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

Wenn Sie jedoch eine Funktion (keine Methode) für die Funktionszusammensetzung oder für Funktionen höherer Ordnung (wie filter(even)) benötigen, generiert der Compiler jedes Mal eine Funktion aus Ihrer Methode, wenn Sie sie als Funktion verwenden, sodass die Leistung möglicherweise etwas schlechter ist als bei val.


Würden Sie sie bitte hinsichtlich der Leistung vergleichen? Ist es nicht wichtig, die Funktion bei jedem evenAufruf zu bewerten?
Amir Karimi

2
defkann verwendet werden, um eine Methode zu definieren, und dies ist die schnellste Option. @ A.Karimi
Anzeigename

2
Zum Spaß: am 2.12 , even eq even.
Som-Snytt

Gibt es ein Konzept für Inline-Funktionen wie in C ++? Ich komme aus der C ++ - Welt, also verzeihen Sie meine Unwissenheit.
animageofmine

2
@animageofmine Der Scala-Compiler kann versuchen, Methoden zu integrieren. Dafür gibt es ein @inlineAttribut . Funktionen können jedoch nicht inline geschaltet werden, da der Funktionsaufruf ein Aufruf der virtuellen applyMethode eines Funktionsobjekts ist. JVM kann solche Anrufe in einigen Situationen devirtualisieren und einbinden, jedoch nicht im Allgemeinen.
Senia

24

Bedenken Sie:

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

Sehen Sie den Unterschied? Zusamenfassend:

def : Bei jedem Aufruf von evenwird der Hauptteil der evenMethode erneut aufgerufen . Bei even2ie val wird die Funktion jedoch nur einmal während der Deklaration initialisiert (und wird daher valin Zeile 4 und nie wieder gedruckt ), und bei jedem Zugriff wird dieselbe Ausgabe verwendet. Versuchen Sie zum Beispiel Folgendes:

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

Bei der xInitialisierung wird der von zurückgegebene Wert Random.nextIntals Endwert von festgelegt x. Wenn das nächste Mal xerneut verwendet wird, wird immer der gleiche Wert zurückgegeben.

Sie können auch träge initialisieren x. dh bei der ersten Verwendung wird es initialisiert und nicht während der Deklaration. Beispielsweise:

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

scala> y
res4: Int = 323930673

scala> y
res5: Int = 323930673

6
Ich denke, Ihre Erklärung könnte etwas implizieren, das Sie nicht beabsichtigen. Versuchen Sie even2zweimal anzurufen , einmal mit 1und einmal mit 2. Sie erhalten bei jedem Anruf unterschiedliche Antworten. Während das printlnin nachfolgenden Aufrufen nicht ausgeführt wird, erhalten Sie nicht das gleiche Ergebnis aus verschiedenen Aufrufen von even2. Warum das printlnnicht noch einmal ausgeführt wird, ist eine andere Frage.
Melston

1
das ist eigentlich sehr interessant. Es ist wie im Fall von val, dh gerade2, der Wert wird zu einem parametrisierten Wert ausgewertet. also ja mit einem val du die auswertung der funktion, deren wert. Der Ausdruck ist nicht Teil des ausgewerteten Wertes. Es ist Teil der Bewertung, aber nicht der bewertete Wert. Der Trick dabei ist, dass der ausgewertete Wert tatsächlich ein parametrisierter Wert ist, der von einer Eingabe abhängt. kluges Ding
MaatDeamon

1
@melston genau! Das habe ich verstanden. Warum wird der Druck nicht erneut ausgeführt, während sich die Ausgabe ändert?
aur

1
@aur Was von Even2 zurückgegeben wird, ist eigentlich eine Funktion (der Ausdruck in Klammern am Ende der Definition von Even2). Diese Funktion wird tatsächlich mit dem Parameter aufgerufen, den Sie bei jedem Aufruf an even2 übergeben.
Melston

5

Sieh dir das an:

  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)

Überraschenderweise wird dies 4 und nicht 9 drucken! val (auch var) wird sofort ausgewertet und zugewiesen.
Ändern Sie nun val in def .. es wird 9 gedruckt! Def ist ein Funktionsaufruf. Er wird bei jedem Aufruf ausgewertet.


1

val dh "sq" ist per Scala definiert. Es wird direkt zum Zeitpunkt der Deklaration ausgewertet, Sie können es später nicht mehr ändern. In anderen Beispielen, in denen auch2 ebenfalls val ist, aber mit der Funktionssignatur deklariert wurde, dh "(Int => Boolean)", ist es also kein Int-Typ. Es ist eine Funktion und ihr Wert wird durch folgenden Ausdruck festgelegt

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

Gemäß der Scala val-Eigenschaft können Sie Even2 keine andere Funktion zuweisen, dieselbe Regel wie sq.

Warum ruft die Funktion eval2 val nicht immer wieder "val" auf?

Ursprungscode:

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

Wir wissen, dass in Scala die letzte Aussage des obigen Ausdrucks (innerhalb von {..}) tatsächlich auf die linke Seite zurückkehrt. Am Ende setzen Sie Even2 auf die Funktion "x => x% 2 == 0", die mit dem Typ übereinstimmt, den Sie für den Val-Typ "Even2" deklariert haben, dh (Int => Boolean), sodass der Compiler zufrieden ist. Jetzt zeigt sogar2 nur noch auf die Funktion "(x => x% 2 == 0)" (keine andere Anweisung vor dh println ("val") usw. Wenn Sie event2 mit verschiedenen Parametern aufrufen, wird tatsächlich "(x => x% 2" aufgerufen == 0) "Code, da nur dieser mit event2 gespeichert wird.

scala> even2(2)
res7: Boolean = true

scala> even2(3)
res8: Boolean = false

Um dies näher zu verdeutlichen, folgt eine andere Version des Codes.

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

Was wird passieren ? hier wird immer wieder "inside final fn" gedruckt, wenn Sie Even2 () aufrufen.

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

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

scala> 

1

Durch Ausführen einer Definition wie def x = ewird der Ausdruck nicht ausgewertet. E. Stattdessen wird e ausgewertet, wenn x aufgerufen wird.

Alternativ bietet Scala eine Wertedefinition an val x = e, die die rechte Seite als Teil der Bewertung der Definition bewertet. Wenn x anschließend verwendet wird, wird es sofort durch den vorberechneten Wert von e ersetzt, sodass der Ausdruck nicht erneut ausgewertet werden muss.


0

Val ist auch eine Bewertung nach Wert. Dies bedeutet, dass der Ausdruck auf der rechten Seite während der Definition ausgewertet wird. Wobei Def durch Namensauswertung ist. Es wird nicht ausgewertet, bis es verwendet wird.


0

Zusätzlich zu den oben genannten hilfreichen Antworten sind meine Ergebnisse:

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

Das Obige zeigt, dass "def" eine Methode (mit Nullargumentparametern) ist, die beim Aufrufen eine andere Funktion "Int => Int" zurückgibt.

Die Konvertierung von Methoden in Funktionen wird hier ausführlich erläutert: 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 bedeutet call-by-name, auf Anfrage ausgewertet

val bedeutet call-by-value, während der Initialisierung ausgewertet


Bei einer so alten Frage und bei so vielen bereits eingereichten Antworten ist es oft hilfreich zu erklären, wie sich Ihre Antwort von den Informationen in den vorhandenen Antworten unterscheidet oder diese ergänzt.
JWVH
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.