Ik probeer een functie weer te geven die geen argumenten nodig heeft en geen waarde retourneert (ik simuleer de setTimeout-functie in JavaScript, als je dat wilt weten.)
case class Scheduled(time : Int, callback : => Unit)
compileert niet, zeggende ” `val’-parameters mogen niet op naam worden genoemd”
case class Scheduled(time : Int, callback : () => Unit)
compileert, maar moet vreemd worden aangeroepen in plaats van
Scheduled(40, { println("x") } )
Ik moet dit doen
Scheduled(40, { () => println("x") } )
Wat ook werkt is
class Scheduled(time : Int, callback : Unit => Unit)
maar wordt op een nog minder verstandige manier aangeroepen
Scheduled(40, { x : Unit => println("x") } )
(Wat zou een variabele van het type Unit zijn?) Wat ik natuurlijk wilis een constructor die kan worden aangeroepen zoals ik hem zou aanroepen als het een gewone functie was:
Scheduled(40, println("x") )
Geef baby zijn flesje!
Antwoord 1, autoriteit 100%
Bellen op naam: => Typ
De => Type
-notatie staat voor call-by-name, wat een van de vele manieren isparameters kunnen worden doorgegeven. Als je er niet bekend mee bent, raad ik je aan wat tijd te nemen om dat wikipedia-artikel te lezen, ook al is het tegenwoordig meestal call-by-value en call-by-reference.
Wat het betekent is dat wat wordt doorgegeven, vervangtvoor de waardenaam binnen de functie. Neem bijvoorbeeld deze functie:
def f(x: => Int) = x * x
Als ik het zo noem
var y = 0
f { y += 1; y }
Dan wordt de code zo uitgevoerd
{ y += 1; y } * { y += 1; y }
Hoewel dat het punt benadrukt van wat er gebeurt als er een id-naamconflict is. In traditionele call-by-name vindt een mechanisme plaats genaamd capture-avoiding substitutie om naamconflicten te voorkomen. In Scala wordt dit echter op een andere manier geïmplementeerd met hetzelfde resultaat — identifier-namen binnen de parameter kunnen niet verwijzen naar of schaduw-ID’s in de aangeroepen functie.
Er zijn nog enkele andere punten met betrekking tot call-by-name waarover ik zal spreken nadat ik de andere twee heb uitgelegd.
0-ariteit Functies: () => Typ
De syntaxis () => Type
staat voor het type van een Function0
. Dat wil zeggen, een functie die geen parameters nodig heeft en iets teruggeeft. Dit komt overeen met, laten we zeggen, het aanroepen van de methode size()
— er zijn geen parameters voor nodig en retourneert een getal.
Het is echter interessant dat deze syntaxis erg lijkt op de syntaxis voor een anonieme letterlijke functie, wat voor enige verwarring zorgt. Bijvoorbeeld,
() => println("I'm an anonymous function")
is een anonieme functie letterlijk van arity 0, waarvan het typeis
() => Unit
Dus we kunnen schrijven:
val f: () => Unit = () => println("I'm an anonymous function")
Het is echter belangrijk om het type niet te verwarren met de waarde.
Eenheid => Typ
Dit is eigenlijk gewoon een Function1
, waarvan de eerste parameter van het type Unit
is. Andere manieren om het te schrijven zijn (Unit) => Type
of Function1[Unit, Type]
. Het punt is… het is onwaarschijnlijk dat dit ooit is wat men wil. Het hoofddoel van het type Unit
is het aangeven van een waarde waarin men niet geïnteresseerd is, dus het heeft geen zin om die waarde te ontvangen.
Denk bijvoorbeeld aan
def f(x: Unit) = ...
Wat zou je kunnen doen met x
? Het kan maar één waarde hebben, dus je hoeft het niet te ontvangen. Een mogelijk gebruik is het koppelen van functies die Unit
retourneren:
val f = (x: Unit) => println("I'm f")
val g = (x: Unit) => println("I'm g")
val h = f andThen g
Omdat andThen
alleen is gedefinieerd op Function1
, en de functies die we koppelen Unit
retourneren, moesten we ze definiëren als zijnde van typ Function1[Unit, Unit]
om ze te kunnen koppelen.
Bronnen van verwarring
De eerste bron van verwarring is te denken dat de overeenkomst tussen type en letterlijke die bestaat voor 0-arity-functies ook bestaat voor call-by-name. Met andere woorden, dat denken, omdat
() => { println("Hi!") }
is een letterlijke waarde voor () => Unit
, dan
{ println("Hi!") }
zou een letterlijke waarde zijn voor => Unit
. Het is niet. Dat is een codeblok, geen letterlijke.
Een andere bron van verwarring is dat de waardevan het Unit
-type wordt geschreven als ()
, wat eruitziet als een parameterlijst met 0-ariteit (maar het is niet).
Antwoord 2, autoriteit 16%
case class Scheduled(time : Int, callback : => Unit)
De modifier case
maakt impliciet val
uit elk argument aan de constructor. Daarom (zoals iemand opmerkte) als je case
verwijdert, kun je een call-by-name-parameter gebruiken. De compiler zou het waarschijnlijk toch toestaan, maar het zou mensen kunnen verbazen als het val callback
creëerde in plaats van te veranderen in lazy val callback
.
Als je verandert naar callback: () => Unit
nu neemt uw geval gewoon een functie in plaats van een call-by-name-parameter. Uiteraard kan de functie worden opgeslagen in val callback
, dus er is geen probleem.
De gemakkelijkste manier om te krijgen wat je wilt (Scheduled(40, println("x") )
waarbij een call-by-name-parameter wordt gebruikt om een lambda door te geven) is waarschijnlijk om de case
en maak expliciet de apply
die je in de eerste plaats niet kon krijgen:
class Scheduled(val time: Int, val callback: () => Unit) {
def doit = callback()
}
object Scheduled {
def apply(time: Int, callback: => Unit) =
new Scheduled(time, { () => callback })
}
In gebruik:
scala> Scheduled(1234, println("x"))
res0: Scheduled = Scheduled@5eb10190
scala> Scheduled(1234, println("x")).doit
x
Antwoord 3
In de vraag wil je de SetTimeOut-functie in JavaScript simuleren. Op basis van eerdere antwoorden schrijf ik de volgende code:
class Scheduled(time: Int, cb: => Unit) {
private def runCb = cb
}
object Scheduled {
def apply(time: Int, cb: => Unit) = {
val instance = new Scheduled(time, cb)
Thread.sleep(time*1000)
instance.runCb
}
}
In REPL kunnen we zoiets als dit krijgen:
scala> Scheduled(10, println("a")); Scheduled(1, println("b"))
a
b
Onze simulatie gedraagt zich niet precies hetzelfde als SetTimeOut, omdat onze simulatie de functie blokkeert, maar SetTimeOut blokkeert niet.
Antwoord 4
Ik doe het op deze manier (ik wil het toepassen niet verbreken):
case class Thing[A](..., lazy: () => A) {}
object Thing {
def of[A](..., a: => A): Thing[A] = Thing(..., () => a)
}
en noem het
Thing.of(..., your_value)