scala - operator - que significa la flecha en php
¿Cuál es la diferencia entre=>,()=> y Unidad=> (2)
Llamada por nombre: => Tipo
La notación de => Type
significa call-by-name, que es una de las muchas maneras en que se pueden pasar los parámetros. Si no está familiarizado con ellos, le recomiendo que se tome un tiempo para leer el artículo de la wikipedia, a pesar de que hoy en día se trata principalmente de llamadas por valor y llamadas 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, toma 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 problema de lo que sucede si hay un nombre de identificación choque. 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 referirse a los identificadores de sombra en la función llamada.
Hay 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 una Function0
. Es decir, una función que no toma parámetros y devuelve algo. Esto es equivalente a, por ejemplo, llamar al size()
del método size()
: no toma parámetros y devuelve un número.
Sin embargo, es interesante que esta sintaxis es muy similar a la sintaxis para un literal de función anónima , que es la causa 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 realmente solo una 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 propósito principal del tipo de Unit
es indicar un valor que no le interesa, por lo que no tiene sentido recibir ese valor.
Considera, por ejemplo,
def f(x: Unit) = ...
¿Qué podría uno hacer con x
? Solo puede tener un valor único, por lo que no es necesario recibirlo. Un uso posible sería encadenar funciones devolviendo la Unit
:
val f = (x: Unit) => println("I''m f")
val g = (x: Unit) => println("I''m g")
val h = f andThen g
Debido a que, y andThen
solo se define en la Function1
, y las funciones que estamos encadenando devuelven la Unit
, tuvimos que definirlas como del 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 0-arity también existe para la llamada por nombre. En otras palabras, pensando eso, porque
() => { println("Hi!") }
es un literal para () => Unit
, luego
{ 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 el valor del tipo de Unit
está escrito ()
, que se parece a una lista de parámetros de aria 0 (pero no lo es).
Intento representar una función que no toma argumentos y no devuelve ningún valor (estoy simulando la función setTimeout en JavaScript, si es necesario).
case class Scheduled(time : Int, callback : => Unit)
no compila, diciendo "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 del tipo Unidad?) Lo que quiero, por supuesto, es un constructor que se pueda invocar de la manera en que lo invocaría si fuera una función común:
Scheduled(40, println("x") )
¡Dale al bebé su botella!
case class Scheduled(time : Int, callback : => Unit)
El modificador de case
hace val
implícito de cada argumento para el constructor. Por lo tanto, como señaló alguien, si elimina el case
, puede usar un parámetro de llamada por nombre. El compilador probablemente podría permitirlo de todos modos, pero podría sorprender a las personas si creara la val callback
de val callback
lugar de transformarse en lazy val callback
.
Cuando cambia a callback: () => Unit
de callback: () => Unit
ahora su caso solo toma una función en lugar de un parámetro de llamada por nombre. Obviamente, la función puede almacenarse en val callback
por lo que no hay problema.
La forma más sencilla 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 el case
y crear explícitamente la apply
que no pudo. 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