¿Cuál es la diferencia entre =>, ()=> y Unidad=>?

Resuelto Michael Lorton asked hace 13 años • 4 respuestas

Estoy tratando de representar una función que no acepta argumentos y no devuelve ningún valor (estoy simulando la función setTimeout en JavaScript, si es necesario saberlo).

case class Scheduled(time : Int, callback :  => Unit)

no se compila y dice "los parámetros `val' pueden no ser llamados por nombre"

case class Scheduled(time : Int, callback :  () => Unit)  

compila, pero tiene que ser invocado de forma extraña, en lugar de

Scheduled(40, { println("x") } )

Tengo que hacer esto

Scheduled(40, { () => println("x") } )      

Lo que también funciona es

class Scheduled(time : Int, callback :  Unit => Unit)

pero se invoca de una manera aún menos sensata

 Scheduled(40, { x : Unit => println("x") } )

(¿Qué sería una variable de tipo Unidad?) Lo que quiero, por supuesto, es un constructor que pueda invocarse de la misma manera que lo haría si fuera una función ordinaria:

 Scheduled(40, println("x") )

¡Dale al bebé su biberón!

Michael Lorton avatar Dec 28 '10 09:12 Michael Lorton
Aceptado

Llamada por nombre: => Tipo

La => Typenotación significa llamada por nombre, que es una de las muchas formas en que se pueden pasar 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 se trata principalmente de 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 la cuestión de lo que sucede si hay un conflicto de nombres de identificadores. En la llamada por nombre tradicional, se lleva a cabo un mecanismo llamado sustitución que evita 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 hacer referencia a los identificadores ocultos 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 de aridad 0: () => Tipo

La sintaxis () => Typerepresenta el tipo de Function0. Es decir, una función que no toma parámetros y devuelve algo. Esto equivale, por ejemplo, a llamar al método size(): no toma parámetros y devuelve un número.

Es interesante, sin embargo, que esta sintaxis sea muy similar a la sintaxis de una función literal anónima , lo que genera cierta confusión. Por ejemplo,

() => println("I'm an anonymous function")

es una función anónima literal de aridad 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

En realidad, esto es solo un Function1cuyo primer parámetro es de tipo Unit. Otras formas de escribirlo serían (Unit) => Typeo Function1[Unit, Type]. La cuestión es... es poco probable que esto sea alguna vez lo que uno quiere. El Unitpropósito principal del tipo es indicar un valor que no le interesa, por lo que no tiene sentido recibir ese valor.

Consideremos, por ejemplo,

def f(x: Unit) = ...

¿ Qué se podría hacer con x? Solo puede tener un valor único, por lo que no es necesario recibirlo. Un posible uso sería encadenar funciones que devuelvan Unit:

val f = (x: Unit) => println("I'm f")
val g = (x: Unit) => println("I'm g")
val h = f andThen g

Como andThensolo está definido 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 tipo y literal que existe para funciones de aridad 0 también existe para llamada por nombre. En otras palabras, pensar que, porque

() => { println("Hi!") }

es un literal para () => Unit, entonces

{ println("Hi!") }

sería un literal para => Unit. No lo es. Eso es un bloque de código , no un literal.

Otra fuente de confusión es que se escribe el valorUnit del tipo , que parece una lista de parámetros de aridad 0 (pero no lo es).()

Daniel C. Sobral avatar Dec 28 '2010 11:12 Daniel C. Sobral
case class Scheduled(time : Int, callback :  => Unit)

El casemodificador hace implícito valcada argumento para el constructor. Por lo tanto (como alguien señaló), si elimina, casepuede usar un parámetro de llamada por nombre. El compilador probablemente podría permitirlo de todos modos, pero podría sorprender a la gente si creara val callbacken lugar de transformarse en lazy val callback.

Cuando cambia a callback: () => Unitahora, su caso simplemente toma una función en lugar de un parámetro de llamada por nombre. Obviamente la función se puede almacenar val callbackasí que no hay problema.

La forma más fácil de obtener lo que desea ( Scheduled(40, println("x") )donde se usa un parámetro de llamada por nombre para pasar una lambda) es probablemente omitir casey crear explícitamente lo applyque no pudo obtener en primer lugar:

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

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

En uso:

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

scala> Scheduled(1234, println("x")).doit
x
Ben Jackson avatar Dec 28 '2010 02:12 Ben Jackson

En la pregunta, desea simular la función SetTimeOut en JavaScript. Basado en respuestas anteriores, escribo el siguiente 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
  }
}

En REPL, podemos obtener algo como esto:

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

Nuestra simulación no se comporta exactamente igual que SetTimeOut, porque nuestra simulación es una función de bloqueo, pero SetTimeOut no es de bloqueo.

Jeff Xu avatar Sep 14 '2018 20:09 Jeff Xu