Call-by-Name: => Typ
Die => Type
Notation steht für Call-by-Name. Dies ist eine der vielen Möglichkeiten, wie Parameter übergeben werden können. Wenn Sie mit ihnen nicht vertraut sind, empfehle ich Ihnen, sich etwas Zeit zu nehmen, um diesen Wikipedia-Artikel zu lesen, obwohl es heutzutage meistens Call-by-Value und Call-by-Reference ist.
Was es bedeutet, dass das, was passiert ist , ersetzt für die Wertnamen innerhalb der Funktion. Nehmen Sie zum Beispiel diese Funktion:
def f(x: => Int) = x * x
Wenn ich es so nenne
var y = 0
f { y += 1; y }
Dann wird der Code so ausgeführt
{ y += 1; y } * { y += 1; y }
Dies wirft jedoch den Punkt auf, was passiert, wenn ein Identifikationsnamenskonflikt auftritt. Bei herkömmlichen Call-by-Name-Vorgängen findet ein Mechanismus statt, der als Substitution zur Vermeidung von Captures bezeichnet wird, um Namenskonflikte zu vermeiden. In Scala wird dies jedoch auf andere Weise mit demselben Ergebnis implementiert - Bezeichnernamen innerhalb des Parameters können in der aufgerufenen Funktion nicht auf Bezeichner verweisen oder diese beschatten.
Es gibt einige andere Punkte im Zusammenhang mit Call-by-Name, von denen ich sprechen werde, nachdem ich die beiden anderen erklärt habe.
0-arity-Funktionen: () => Typ
Die Syntax () => Type
steht für den Typ a Function0
. Das heißt, eine Funktion, die keine Parameter akzeptiert und etwas zurückgibt. Dies entspricht beispielsweise dem Aufruf der Methode size()
- es werden keine Parameter verwendet und eine Zahl zurückgegeben.
Es ist jedoch interessant, dass diese Syntax der Syntax für ein anonymes Funktionsliteral sehr ähnlich ist , was zu Verwirrung führt. Beispielsweise,
() => println("I'm an anonymous function")
ist ein anonymes Funktionsliteral der Arität 0, dessen Typ ist
() => Unit
Also könnten wir schreiben:
val f: () => Unit = () => println("I'm an anonymous function")
Es ist jedoch wichtig, den Typ nicht mit dem Wert zu verwechseln.
Einheit => Typ
Dies ist eigentlich nur ein Function1
, dessen erster Parameter vom Typ ist Unit
. Andere Möglichkeiten, es zu schreiben, wären (Unit) => Type
oder Function1[Unit, Type]
. Die Sache ist ... es ist unwahrscheinlich, dass dies jemals das ist, was man will. Der Unit
Hauptzweck des Typs besteht darin, einen Wert anzugeben, an dem man nicht interessiert ist. Daher ist es nicht sinnvoll, diesen Wert zu erhalten .
Betrachten Sie zum Beispiel
def f(x: Unit) = ...
Was könnte man möglicherweise damit machen x
? Es kann nur einen einzigen Wert haben, daher muss man ihn nicht erhalten. Eine mögliche Verwendung wäre die Rückgabe von Verkettungsfunktionen Unit
:
val f = (x: Unit) => println("I'm f")
val g = (x: Unit) => println("I'm g")
val h = f andThen g
Da andThen
nur für definiert Function1
ist und die Funktionen, die wir verketten, zurückkehren Unit
, mussten wir sie als vom Typ definieren, um sie verketten Function1[Unit, Unit]
zu können.
Quellen der Verwirrung
Die erste Quelle der Verwirrung ist der Gedanke, dass die Ähnlichkeit zwischen Typ und Literal, die für 0-Arity-Funktionen besteht, auch für Call-by-Name besteht. Mit anderen Worten, das zu denken, weil
() => { println("Hi!") }
für eine wörtliche () => Unit
, dann
{ println("Hi!") }
wäre ein wörtliches für => Unit
. Es ist nicht. Das ist ein Codeblock , kein Literal.
Eine weitere Quelle der Verwirrung ist, dass Unit
der Wert des Typs geschrieben wird ()
, der wie eine Parameterliste mit 0 Aritäten aussieht (aber nicht).
case class Scheduled(time: Int)(callback: => Unit)
. Dies funktioniert, weil die sekundäre Parameterliste weder öffentlich verfügbar gemacht noch in den generiertenequals
/hashCode
Methoden enthalten ist.