Chamada por nome: => Tipo
A => Type
notação significa chamada por nome, que é uma das muitas maneiras pelas quais os parâmetros podem ser transmitidos. Se você não estiver familiarizado com eles, recomendo reservar um tempo para ler esse artigo da Wikipedia, mesmo que hoje em dia seja principalmente por valor e por referência.
O que isso significa é que o que é passado é substituído pelo nome do valor dentro da função. Por exemplo, considere esta função:
def f(x: => Int) = x * x
Se eu chamar assim
var y = 0
f { y += 1; y }
Então o código será executado assim
{ y += 1; y } * { y += 1; y }
Embora isso levante o ponto do que acontece se houver um conflito de nome de identificador. Na chamada tradicional por nome, um mecanismo chamado substituição para evitar captura ocorre para evitar conflitos de nome. No Scala, no entanto, isso é implementado de outra maneira com o mesmo resultado - nomes de identificadores dentro do parâmetro não podem se referir ou identificadores de sombra na função chamada.
Existem alguns outros pontos relacionados à chamada por nome dos quais falarei depois de explicar os outros dois.
Funções de aridade 0: () => Tipo
A sintaxe () => Type
representa o tipo de a Function0
. Ou seja, uma função que não aceita parâmetros e retorna algo. Isso é equivalente a, digamos, chamar o método size()
- ele não usa parâmetros e retorna um número.
É interessante, no entanto, que essa sintaxe seja muito semelhante à sintaxe para uma literal de função anônima , que é a causa de alguma confusão. Por exemplo,
() => println("I'm an anonymous function")
é uma função anônima literal de arity 0, cujo tipo é
() => Unit
Para que pudéssemos escrever:
val f: () => Unit = () => println("I'm an anonymous function")
É importante não confundir o tipo com o valor, no entanto.
Unidade => Tipo
Esta é realmente apenas um Function1
, cujo primeiro parâmetro é do tipo Unit
. Outras maneiras de escrever seria (Unit) => Type
ou Function1[Unit, Type]
. A questão é ... é improvável que isso seja o que se quer. O Unit
objetivo principal do tipo é indicar um valor pelo qual não se interessa, portanto, não faz sentido receber esse valor.
Considere, por exemplo,
def f(x: Unit) = ...
O que alguém poderia fazer x
? Ele pode ter apenas um único valor, portanto, não é necessário recebê-lo. Um possível uso seria o encadeamento de funções retornando Unit
:
val f = (x: Unit) => println("I'm f")
val g = (x: Unit) => println("I'm g")
val h = f andThen g
Como andThen
só está definido Function1
e as funções que estamos encadeando estão retornando Unit
, tivemos que defini-las como sendo do tipo Function1[Unit, Unit]
para poder encadear elas.
Fontes de confusão
A primeira fonte de confusão é pensar que a semelhança entre tipo e literal que existe para funções de aridade 0 também existe para chamada por nome. Em outras palavras, pensando que, porque
() => { println("Hi!") }
é literal para () => Unit
, então
{ println("Hi!") }
seria um literal para => Unit
. Não é. Isso é um bloco de código , não um literal.
Outra fonte de confusão é que Unit
o valor desse tipo é gravado ()
, que se parece com uma lista de parâmetros de 0-arity (mas não é).
case class Scheduled(time: Int)(callback: => Unit)
. Isso funciona porque a lista de parâmetros secundários não é exposta publicamente, nem é incluída nos métodosequals
/ geradoshashCode
.