Qual é a diferença entre =>, () => e Unit =>

153

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ê!

Malvolio
fonte
3
Outra maneira de usar classes de caso com parms por nome é colocá-las em uma lista de parâmetros secundária, por exemplo 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étodos equals/ gerados hashCode.
N
Alguns aspectos mais interessantes sobre as diferenças entre parâmetros por nome e funções de aridade 0 são encontrados nesta pergunta e na resposta. Na verdade, era o que eu estava procurando quando encontrei essa pergunta.
Lex82 31/01

Respostas:

234

Chamada por nome: => Tipo

A => Typenotaçã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 () => Typerepresenta 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) => Typeou Function1[Unit, Type]. A questão é ... é improvável que isso seja o que se quer. O Unitobjetivo 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 andThensó está definido Function1e 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 Unito valor desse tipo é gravado (), que se parece com uma lista de parâmetros de 0-arity (mas não é).

Daniel C. Sobral
fonte
Talvez eu tenha que ser o primeiro a votar em baixa após dois anos. Alguém está se perguntando sobre a sintaxe case => no Natal, e não posso recomendar esta resposta como canônica e completa! Para o que o mundo está chegando? Talvez os maias estivessem de folga por uma semana. Eles apareceram corretamente nos anos bissextos? Horário de verão?
som-snytt
@ som-snytt Bem, a pergunta não fez perguntas case ... =>, então eu não mencionei. Triste mas verdadeiro. :-)
Daniel C. Sobral
1
@ Daniel C. Sobral, você poderia explicar "Esse é um bloco de código, não um literal". parte. Então, qual é a diferença exata entre dois?
Nish1013 04/04
2
@ nish1013 Um "literal" é um valor (o número inteiro 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.
Daniel C. Sobral
1
@ Alex Essa é a mesma diferença que (Unit) => Typevs () => Type- o primeiro é a Function1[Unit, Type], enquanto o segundo é a Function0[Type].
Daniel C. Sobral
36
case class Scheduled(time : Int, callback :  => Unit)

O casemodificador torna implícito a valpartir de cada argumento para o construtor. Portanto (como alguém observou), se você remover, casepoderá 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, em val callbackvez de se transformar em lazy val callback.

Quando você muda para callback: () => Unitagora, 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 callbackentã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 ignorar casee criar explicitamente o applyque você não conseguiu em primeiro lugar:

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

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

Em uso:

scala> Scheduled(1234, println("x"))
res0: Scheduled = Scheduled@5eb10190

scala> Scheduled(1234, println("x")).doit
x
Ben Jackson
fonte
3
Por que não manter uma classe de caso e simplesmente substituir a aplicação padrão? Além disso, o compilador não pode traduzir um nome por uma val preguiçoso, uma vez que têm inerentemente diferentes semântica, preguiçoso é at-mais-uma vez por nome tem at-cada-referência
Viktor Klang
@ViktorKlang Como você pode substituir o método de aplicação padrão de uma classe de caso? stackoverflow.com/questions/2660975/…
Sawyer
objeto ClassName {def apply (…):… =…}
Viktor Klang
Quatro anos depois, percebo que a resposta que selecionei apenas respondeu à pergunta do título, e não a que eu realmente tinha (que esta responde).
Malvolio
1

Na pergunta, você deseja simular a função SetTimeOut em JavaScript. Com base nas respostas anteriores, escrevo o seguinte código:

class Scheduled(time: Int, cb: => Unit) {
  private def runCb = cb
}

object Scheduled {
  def apply(time: Int, cb: => Unit) = {
    val instance = new Scheduled(time, cb)
    Thread.sleep(time*1000)
    instance.runCb
  }
}

No REPL, podemos obter algo assim:

scala> Scheduled(10, println("a")); Scheduled(1, println("b"))
a
b

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.

Jeff Xu
fonte
0

Eu faço desta maneira (apenas não quero interromper a aplicação):

case class Thing[A](..., lazy: () => A) {}
object Thing {
  def of[A](..., a: => A): Thing[A] = Thing(..., () => a)
}

e chame

Thing.of(..., your_value)
Alexey Rykhalskiy
fonte