Qual è la differenza tra =>, () => e Unità =>

Sto cercando di rappresentare una funzione che non accetta argomenti e non restituisce alcun valore (sto simulando la funzione setTimeout in JavaScript, se devi saperlo.)

case class Scheduled(time : Int, callback : => Unit) 

non si compila, dicendo “i parametri ‘val’ potrebbero non essere call-by-name”

 case class Scheduled(time : Int, callback : () => Unit) 

compila, ma deve essere invocato stranamente, invece di

 Scheduled(40, { println("x") } ) 

devo fare questo

 Scheduled(40, { () => println("x") } ) 

Ciò che funziona anche è

 class Scheduled(time : Int, callback : Unit => Unit) 

ma è invocato in un modo ancora meno sensibile

  Scheduled(40, { x : Unit => println("x") } ) 

(Che cosa sarebbe una variabile di tipo Unit?) Quello che voglio naturalmente è un costruttore che può essere invocato come se lo invocassi se fosse una funzione ordinaria:

  Scheduled(40, println("x") ) 

Dà al bambino la sua bottiglia!

Call-by-Name: => Type

La notazione => Type sta per call-by-name, che è uno dei molti modi in cui i parametri possono essere passati. Se non hai familiarità con loro, ti consiglio di prendere un po ‘di tempo per leggere l’articolo di wikipedia, anche se oggigiorno è per lo più call-by-value e call-by-reference.

Ciò che significa è che ciò che viene passato è sostituito dal nome del valore all’interno della funzione. Ad esempio, accetta questa funzione:

 def f(x: => Int) = x * x 

Se lo chiamo così

 var y = 0 f { y += 1; y } 

Quindi il codice verrà eseguito in questo modo

 { y += 1; y } * { y += 1; y } 

Anche se ciò solleva il punto di ciò che accade se c’è un conflitto di nomi identificativo. Nella chiamata tradizionale per nome, si verifica un meccanismo chiamato sostituzione evitamento per evitare conflitti di nomi. In Scala, tuttavia, questo viene implementato in un altro modo con lo stesso risultato: i nomi identificativi all’interno del parametro non possono fare riferimento a o identificatori ombra nella funzione chiamata.

Ci sono altri punti relativi alla chiamata per nome di cui parlerò dopo aver spiegato gli altri due.

Funzioni 0-arità: () => Tipo

La syntax () => Type indica il tipo di una Function0 . Cioè, una funzione che non accetta parametri e restituisce qualcosa. Questo è equivalente, ad esempio, chiamando il metodo size() – non accetta parametri e restituisce un numero.

È interessante, tuttavia, che questa syntax sia molto simile alla syntax di una funzione anonimo letterale , che è la causa di una certa confusione. Per esempio,

 () => println("I'm an anonymous function") 

è una funzione anonima letterale di arity 0, il cui tipo è

 () => Unit 

Quindi potremmo scrivere:

 val f: () => Unit = () => println("I'm an anonymous function") 

È importante non confondere il tipo con il valore, tuttavia.

Unità => Tipo

Questo è in realtà solo una Function1 , il cui primo parametro è di tipo Unit . Altri modi per scriverlo sarebbero (Unit) => Type o Function1[Unit, Type] . Il fatto è che … è improbabile che sia mai ciò che si vuole. Lo scopo principale del tipo di Unit è indicare un valore a cui non è interessato, quindi non ha senso ricevere quel valore.

Si consideri, ad esempio,

 def f(x: Unit) = ... 

Cosa si potrebbe fare con x ? Può avere un solo valore, quindi non è necessario riceverlo. Un ansible uso sarebbe concatenare le funzioni che restituiscono Unit :

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

Perché andThen è solo definito su Function1 , e le funzioni che stiamo concatenando stanno restituendo Unit , dovevamo definirle come di tipo Function1[Unit, Unit] per poterle concatenare.

Fonti di confusione

La prima fonte di confusione sta nel pensare che la similarità tra il tipo e il letterale esistente per le funzioni 0-arity esiste anche per la chiamata per nome. In altre parole, pensando che, perché

 () => { println("Hi!") } 

è un letterale per () => Unit , quindi

 { println("Hi!") } 

sarebbe un valore letterale per => Unit . Non è. Questo è un blocco di codice , non letterale.

Un’altra fonte di confusione è che il valore del tipo di Unit è scritto () , che assomiglia ad un elenco di parametri 0-arity (ma non lo è).

 case class Scheduled(time : Int, callback : => Unit) 

Il modificatore di case rende implicito val di ogni argomento al costruttore. Quindi (come notato da qualcuno) se si rimuove il case è ansible utilizzare un parametro call-by-name. Il compilatore potrebbe probabilmente permetterlo comunque, ma potrebbe sorprendere le persone se creava val callback invece di trasformarsi in lazy val callback .

Quando cambi in callback: () => Unit ora il tuo caso prende solo una funzione piuttosto che un parametro call-by-name. Ovviamente la funzione può essere memorizzata in val callback quindi non ci sono problemi.

Il modo più semplice per ottenere quello che vuoi ( Scheduled(40, println("x") ) cui un parametro call-by-name è usato per passare un lambda) è probabilmente saltare il case e creare esplicitamente l’ apply che non potresti t ottenere in primo luogo:

 class Scheduled(val time: Int, val callback: () => Unit) { def doit = callback() } object Scheduled { def apply(time: Int, callback: => Unit) = new Scheduled(time, { () => callback }) } 

In uso:

 scala> Scheduled(1234, println("x")) res0: Scheduled = [email protected] scala> Scheduled(1234, println("x")).doit x