Chiamata per nome: => Tipo
La => Type
notazione 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 dedicare un po 'di tempo a leggere l'articolo di Wikipedia, anche se al giorno d'oggi è principalmente chiamata per valore e chiamata per riferimento.
Ciò che significa è che ciò che viene passato viene sostituito con il nome valore all'interno della funzione. Ad esempio, prendi 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 }
Tuttavia, ciò solleva il punto su ciò che accade se esiste uno scontro di nomi identificativi. Nella tradizionale chiamata per nome, un meccanismo chiamato sostituzione che evita la cattura ha luogo per evitare scontri tra nomi. In Scala, tuttavia, questo è implementato in un altro modo con lo stesso risultato: i nomi degli identificatori all'interno del parametro non possono fare riferimento o gli 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-arity: () => Tipo
La sintassi () => Type
sta per il tipo di a Function0
. Cioè, una funzione che non accetta parametri e restituisce qualcosa. Ciò equivale, per esempio, a chiamare il metodo size()
: non accetta parametri e restituisce un numero.
È interessante, tuttavia, che questa sintassi è molto simile alla sintassi per una funzione anonima letterale , che è la causa di un po 'di 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")
Tuttavia, è importante non confondere il tipo con il valore.
Unità => Tipo
Questo è in realtà solo un Function1
, il cui primo parametro è di tipo Unit
. Altri modi per scrivere sarebbe (Unit) => Type
o Function1[Unit, Type]
. Il fatto è ... è improbabile che questo sia mai ciò che si vuole. Lo Unit
scopo principale del tipo è quello di indicare un valore a cui non è interessato, quindi non ha senso ricevere quel valore.
Considera, ad esempio,
def f(x: Unit) = ...
Che cosa si potrebbe fare con x
? Può avere un solo valore, quindi non è necessario riceverlo. Un possibile utilizzo sarebbe il concatenamento di funzioni che ritornano Unit
:
val f = (x: Unit) => println("I'm f")
val g = (x: Unit) => println("I'm g")
val h = f andThen g
Poiché andThen
è definito solo Function1
e le funzioni che stiamo concatenando stanno tornando Unit
, abbiamo dovuto definirle come di tipo Function1[Unit, Unit]
per poterle incatenare.
Fonti di confusione
La prima fonte di confusione sta pensando che la somiglianza tra tipo e letterale che esiste per le funzioni 0-arity esiste anche per call-by-name. In altre parole, pensando che, perché
() => { println("Hi!") }
è letterale per () => Unit
, quindi
{ println("Hi!") }
sarebbe letterale per => Unit
. Non è. Questo è un blocco di codice , non un letterale.
Un'altra fonte di confusione è Unit
la scrittura del valore di quel tipo ()
, che sembra un elenco di parametri 0-arity (ma non lo è).
case class Scheduled(time: Int)(callback: => Unit)
. Questo funziona perché l'elenco dei parametri secondario non è esposto pubblicamente, né è incluso nei metodiequals
/ generatihashCode
.