Wat is het verschil tussen => , ()=> en Unit=>

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 () => Typestaat 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 Unitis. Andere manieren om het te schrijven zijn (Unit) => Typeof Function1[Unit, Type]. Het punt is… het is onwaarschijnlijk dat dit ooit is wat men wil. Het hoofddoel van het type Unitis 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 Unitretourneren:

val f = (x: Unit) => println("I'm f")
val g = (x: Unit) => println("I'm g")
val h = f andThen g

Omdat andThenalleen is gedefinieerd op Function1, en de functies die we koppelen Unitretourneren, 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 casemaakt impliciet valuit elk argument aan de constructor. Daarom (zoals iemand opmerkte) als je caseverwijdert, kun je een call-by-name-parameter gebruiken. De compiler zou het waarschijnlijk toch toestaan, maar het zou mensen kunnen verbazen als het val callbackcreëerde in plaats van te veranderen in lazy val callback.

Als je verandert naar callback: () => Unitnu 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 caseen maak expliciet de applydie 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)

Other episodes