Estou tentando representar uma função que não aceita argumentos e não retorna valor (estou simulando a função setTimeout em JavaScript, se você deve saber.)
case class Scheduled(time : Int, callback : => Unit)
não é compilado, dizendo "parâmetros` `val 'podem não ser chamados por nome"
case class Scheduled(time : Int, callback : () => Unit)
compila, mas precisa ser invocado de maneira estranha, em vez de
Scheduled(40, { println("x") } )
Eu tenho que fazer isso
Scheduled(40, { () => println("x") } )
O que também funciona é
class Scheduled(time : Int, callback : Unit => Unit)
mas é invocado de uma maneira ainda menos sensível
Scheduled(40, { x : Unit => println("x") } )
(O que seria uma variável do tipo Unit?) O que eu quero , é claro, é um construtor que possa ser chamado da maneira que eu chamaria se fosse uma função comum:
Scheduled(40, println("x") )
Dê a mamadeira ao bebê!
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
.Respostas:
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:
Se eu chamar assim
Então o código será executado assim
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 aFunction0
. Ou seja, uma função que não aceita parâmetros e retorna algo. Isso é equivalente a, digamos, chamar o métodosize()
- 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,
é uma função anônima literal de arity 0, cujo tipo é
Para que pudéssemos escrever:
É importante não confundir o tipo com o valor, no entanto.
Unidade => Tipo
Esta é realmente apenas um
Function1
, cujo primeiro parâmetro é do tipoUnit
. Outras maneiras de escrever seria(Unit) => Type
ouFunction1[Unit, Type]
. A questão é ... é improvável que isso seja o que se quer. OUnit
objetivo principal do tipo é indicar um valor pelo qual não se interessa, portanto, não faz sentido receber esse valor.Considere, por exemplo,
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 retornandoUnit
:Como
andThen
só está definidoFunction1
e as funções que estamos encadeando estão retornandoUnit
, tivemos que defini-las como sendo do tipoFunction1[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
é literal para
() => Unit
, entãoseria 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 é).fonte
case ... =>
, então eu não mencionei. Triste mas verdadeiro. :-)1
, o caractere'a'
, a cadeia de caracteres"abc"
ou a função() => println("here")
, para alguns exemplos). Pode ser passado como argumento, armazenado em variáveis, etc. Um "bloco de código" é uma delimitação sintática de instruções - não é um valor, não pode ser repassado ou algo assim.(Unit) => Type
vs() => Type
- o primeiro é aFunction1[Unit, Type]
, enquanto o segundo é aFunction0[Type]
.O
case
modificador torna implícito aval
partir de cada argumento para o construtor. Portanto (como alguém observou), se você remover,case
poderá usar um parâmetro de chamada por nome. O compilador provavelmente poderia permitir isso de qualquer maneira, mas pode surpreender as pessoas se ele for criado, emval callback
vez de se transformar emlazy val callback
.Quando você muda para
callback: () => Unit
agora, seu caso apenas aceita uma função em vez de um parâmetro de chamada por nome. Obviamente, a função pode ser armazenada,val callback
então não há problema.A maneira mais fácil de obter o que você deseja (
Scheduled(40, println("x") )
onde um parâmetro de chamada por nome é usado para passar um lambda) é provavelmente ignorarcase
e criar explicitamente oapply
que você não conseguiu em primeiro lugar:Em uso:
fonte
Na pergunta, você deseja simular a função SetTimeOut em JavaScript. Com base nas respostas anteriores, escrevo o seguinte código:
No REPL, podemos obter algo assim:
Nossa simulação não se comporta exatamente da mesma forma que SetTimeOut, porque nossa simulação é função de bloqueio, mas SetTimeOut não é de bloqueio.
fonte
Eu faço desta maneira (apenas não quero interromper a aplicação):
e chame
fonte