Llamada por nombre: => Tipo
La => Type
notación significa llamada por nombre, que es una de las muchas formas en que se pueden pasar los parámetros. Si no está familiarizado con ellos, le recomiendo que se tome un tiempo para leer ese artículo de Wikipedia, aunque hoy en día es principalmente llamada por valor y llamada por referencia.
Lo que significa es que lo que se pasa se sustituye por el nombre del valor dentro de la función. Por ejemplo, tome esta función:
def f(x: => Int) = x * x
Si lo llamo así
var y = 0
f { y += 1; y }
Entonces el código se ejecutará así
{ y += 1; y } * { y += 1; y }
Aunque eso plantea el punto de lo que sucede si hay un choque de nombre de identificador. En la llamada tradicional por nombre, se lleva a cabo un mecanismo llamado sustitución para evitar la captura para evitar conflictos de nombres. En Scala, sin embargo, esto se implementa de otra manera con el mismo resultado: los nombres de los identificadores dentro del parámetro no pueden referirse a los identificadores de sombra en la función llamada.
Hay algunos otros puntos relacionados con la llamada por nombre de los que hablaré después de explicar los otros dos.
Funciones 0-arity: () => Tipo
La sintaxis () => Type
representa el tipo de a Function0
. Es decir, una función que no toma parámetros y devuelve algo. Esto es equivalente a, por ejemplo, llamar al métodosize()
: no toma parámetros y devuelve un número.
Sin embargo, es interesante que esta sintaxis es muy similar a la sintaxis de una función literal anónima , lo que es motivo de cierta confusión. Por ejemplo,
() => println("I'm an anonymous function")
es una función anónima literal de arity 0, cuyo tipo es
() => Unit
Entonces podríamos escribir:
val f: () => Unit = () => println("I'm an anonymous function")
Sin embargo, es importante no confundir el tipo con el valor.
Unidad => Tipo
Esto es en realidad solo un Function1
, cuyo primer parámetro es de tipo Unit
. Otras formas de escribirlo serían (Unit) => Type
o Function1[Unit, Type]
. La cosa es ... es poco probable que esto sea lo que uno quiere. El Unit
propósito principal del tipo es indicar un valor que no le interesa, por lo que no tiene sentido recibir ese valor.
Considere, por ejemplo,
def f(x: Unit) = ...
¿Con qué se podría hacer x
? Solo puede tener un valor único, por lo que no es necesario recibirlo. Un posible uso sería encadenar funciones que devuelven Unit
:
val f = (x: Unit) => println("I'm f")
val g = (x: Unit) => println("I'm g")
val h = f andThen g
Debido a andThen
que solo se define en Function1
y las funciones que estamos encadenando están regresando Unit
, tuvimos que definirlas como de tipo Function1[Unit, Unit]
para poder encadenarlas.
Fuentes de confusión
La primera fuente de confusión es pensar que la similitud entre el tipo y el literal que existe para las funciones de aridad 0 también existe para la llamada por nombre. En otras palabras, pensar eso, porque
() => { println("Hi!") }
es un literal para () => Unit
, entonces
{ println("Hi!") }
sería un literal para => Unit
. No lo es. Ese es un bloque de codigo , no un literal.
Otra fuente de confusión es que Unit
el valor de ese tipo está escrito ()
, lo que parece una lista de parámetros de 0 aridades (pero no lo es).
case class Scheduled(time: Int)(callback: => Unit)
. Esto funciona porque la lista de parámetros secundarios no se expone públicamente, ni se incluye en los métodosequals
/ generadoshashCode
.