Ich versuche, eine Funktion darzustellen, die keine Argumente akzeptiert und keinen Wert zurückgibt (ich simuliere die Funktion setTimeout in JavaScript, wenn Sie es wissen müssen.)
case class Scheduled(time : Int, callback : => Unit)
wird nicht kompiliert und sagt, dass "val" -Parameter möglicherweise nicht nach Namen aufgerufen werden "
case class Scheduled(time : Int, callback : () => Unit)
kompiliert, muss aber seltsamerweise aufgerufen werden, anstatt
Scheduled(40, { println("x") } )
Ich muss das tun
Scheduled(40, { () => println("x") } )
Was auch funktioniert ist
class Scheduled(time : Int, callback : Unit => Unit)
wird aber noch weniger vernünftig aufgerufen
Scheduled(40, { x : Unit => println("x") } )
(Was wäre eine Variable vom Typ Unit?) Was ich natürlich möchte, ist ein Konstruktor, der so aufgerufen werden kann, wie ich ihn aufrufen würde, wenn es eine gewöhnliche Funktion wäre:
Scheduled(40, println("x") )
Gib Baby seine Flasche!
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.Antworten:
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:
Wenn ich es so nenne
Dann wird der Code so ausgeführt
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 aFunction0
. Das heißt, eine Funktion, die keine Parameter akzeptiert und etwas zurückgibt. Dies entspricht beispielsweise dem Aufruf der Methodesize()
- 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,
ist ein anonymes Funktionsliteral der Arität 0, dessen Typ ist
Also könnten wir schreiben:
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 istUnit
. Andere Möglichkeiten, es zu schreiben, wären(Unit) => Type
oderFunction1[Unit, Type]
. Die Sache ist ... es ist unwahrscheinlich, dass dies jemals das ist, was man will. DerUnit
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
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 VerkettungsfunktionenUnit
:Da
andThen
nur für definiertFunction1
ist und die Funktionen, die wir verketten, zurückkehrenUnit
, mussten wir sie als vom Typ definieren, um sie verkettenFunction1[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
für eine wörtliche
() => Unit
, dannwä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).quelle
case ... =>
, also habe ich sie nicht erwähnt. Traurig aber wahr. :-)1
, das Zeichen'a'
, die Zeichenfolge"abc"
oder die Funktion() => println("here")
). Es kann als Argument übergeben, in Variablen usw. gespeichert werden. Ein "Codeblock" ist eine syntaktische Abgrenzung von Anweisungen - es ist kein Wert, es kann nicht weitergegeben werden oder so etwas.(Unit) => Type
vs() => Type
- der erste ist aFunction1[Unit, Type]
, während der zweite a istFunction0[Type]
.Der
case
Modifikator impliziertval
aus jedem Argument den Konstruktor. Daher können Sie (wie bereits erwähnt) beim Entfernencase
einen Call-by-Name-Parameter verwenden. Der Compiler könnte es wahrscheinlich trotzdem zulassen, aber es könnte die Leute überraschen, wenn es erstellt wird,val callback
anstatt sich in etwas zu verwandelnlazy val callback
.Wenn Sie zu
callback: () => Unit
jetzt wechseln, übernimmt Ihr Fall nur eine Funktion und keinen Call-by-Name-Parameter. Natürlich kann die Funktion in gespeichert werden,val callback
so dass es kein Problem gibt.Der einfachste Weg, um das zu bekommen, was Sie wollen (
Scheduled(40, println("x") )
wobei ein Call-by-Name-Parameter verwendet wird, um ein Lambda zu übergeben), besteht wahrscheinlich darin, das zu überspringencase
und explizit das zu erstellenapply
, was Sie überhaupt nicht bekommen konnten:In Benutzung:
quelle
In der Frage möchten Sie die SetTimeOut-Funktion in JavaScript simulieren. Basierend auf früheren Antworten schreibe ich folgenden Code:
In REPL können wir so etwas bekommen:
Unsere Simulation verhält sich nicht genau wie SetTimeOut, da unsere Simulation die Funktion blockiert, SetTimeOut jedoch nicht blockiert.
quelle
Ich mache es so (will nur nicht brechen bewerben):
und nenne es
quelle