Cuál es la diferencia entre:
def even: Int => Boolean = _ % 2 == 0
y
val even: Int => Boolean = _ % 2 == 0
Ambos pueden ser llamados como even(10)
.
Cuál es la diferencia entre:
def even: Int => Boolean = _ % 2 == 0
y
val even: Int => Boolean = _ % 2 == 0
Ambos pueden ser llamados como even(10)
.
Respuestas:
El método def even
evalúa la llamada y crea una nueva función cada vez (nueva instancia de Function1
).
def even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = false
val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true
Con def
usted puede obtener una nueva función en cada llamada:
val test: () => Int = {
val r = util.Random.nextInt
() => r
}
test()
// Int = -1049057402
test()
// Int = -1049057402 - same result
def test: () => Int = {
val r = util.Random.nextInt
() => r
}
test()
// Int = -240885810
test()
// Int = -1002157461 - new result
val
evalúa cuando se define, def
- cuando se llama:
scala> val even: Int => Boolean = ???
scala.NotImplementedError: an implementation is missing
scala> def even: Int => Boolean = ???
even: Int => Boolean
scala> even
scala.NotImplementedError: an implementation is missing
Tenga en cuenta que hay una tercera opción: lazy val
.
Se evalúa cuando se llama por primera vez:
scala> lazy val even: Int => Boolean = ???
even: Int => Boolean = <lazy>
scala> even
scala.NotImplementedError: an implementation is missing
Pero devuelve el mismo resultado (en este caso, la misma instancia de FunctionN
) cada vez:
lazy val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true
lazy val test: () => Int = {
val r = util.Random.nextInt
() => r
}
test()
// Int = -1068569869
test()
// Int = -1068569869 - same result
Actuación
val
evalúa cuando se define.
def
evalúa en cada llamada, por lo que el rendimiento podría ser peor que val
para varias llamadas. Obtendrá el mismo rendimiento con una sola llamada. Y sin llamadas no obtendrá gastos generales def
, por lo que puede definirlo incluso si no lo usará en algunas ramas.
Con un lazy val
obtendrá una evaluación perezosa: puede definirlo incluso si no lo usará en algunas ramas, y evalúa una vez o nunca, pero obtendrá un poco de sobrecarga por el bloqueo de doble verificación en cada acceso a su lazy val
.
Como señaló @SargeBorsch, podría definir el método, y esta es la opción más rápida:
def even(i: Int): Boolean = i % 2 == 0
Pero si necesita una función (no un método) para la composición de funciones o para un filter(even)
compilador de funciones de orden superior (como ) generará una función a partir de su método cada vez que la use como función, por lo que el rendimiento podría ser ligeramente peor que con val
.
even
se llama?
def
se puede usar para definir un método, y esta es la opción más rápida. @ A.Karimi
even eq even
.
@inline
atributo para esto. Pero no puede incluir funciones en línea porque la llamada de función es una llamada al apply
método virtual de un objeto de función. JVM podría desvirtualizar e incorporar tales llamadas en algunas situaciones, pero no en general.
Considera esto:
scala> def even: (Int => Boolean) = {
println("def");
(x => x % 2 == 0)
}
even: Int => Boolean
scala> val even2: (Int => Boolean) = {
println("val");
(x => x % 2 == 0)
}
val //gets printed while declaration. line-4
even2: Int => Boolean = <function1>
scala> even(1)
def
res9: Boolean = false
scala> even2(1)
res10: Boolean = false
¿Ves la diferencia? En breve:
def : por cada llamada a even
, vuelve a llamar al cuerpo del even
método. Pero con even2
ie val , la función se inicializa solo una vez durante la declaración (y, por lo tanto, se imprime val
en la línea 4 y nunca más) y se usa la misma salida cada vez que se accede. Por ejemplo, intente hacer esto:
scala> import scala.util.Random
import scala.util.Random
scala> val x = { Random.nextInt }
x: Int = -1307706866
scala> x
res0: Int = -1307706866
scala> x
res1: Int = -1307706866
Cuando x
se inicializa, el valor devuelto por Random.nextInt
se establece como el valor final de x
. La próxima vez que x
se use nuevamente, siempre devolverá el mismo valor.
También puedes inicializar perezosamente x
. es decir, la primera vez que se usa se inicializa y no mientras se declara. Por ejemplo:
scala> lazy val y = { Random.nextInt }
y: Int = <lazy>
scala> y
res4: Int = 323930673
scala> y
res5: Int = 323930673
even2
dos veces, una con 1
y otra con 2
. Obtendrá diferentes respuestas en cada llamada. Entonces, aunque println
no se ejecuta en llamadas posteriores, no obtiene el mismo resultado de diferentes llamadas a even2
. En cuanto a por qué println
no se ejecuta nuevamente, esa es una pregunta diferente.
Mira esto:
var x = 2 // using var as I need to change it to 3 later
val sq = x*x // evaluates right now
x = 3 // no effect! sq is already evaluated
println(sq)
¡Sorprendentemente, esto imprimirá 4 y no 9! val (incluso var) se evalúa inmediatamente y se asigna.
Ahora cambie val a def .. ¡imprimirá 9! Def es una llamada a función ... se evaluará cada vez que se llame.
val es decir, "sq" es, por definición Scala, es fijo. Se evalúa justo en el momento de la declaración, no se puede cambiar más tarde. En otros ejemplos, donde even2 también es val, pero se declaró con la firma de la función, es decir, "(Int => Boolean)", por lo que no es de tipo Int. Es una función y su valor se establece siguiendo la expresión
{
println("val");
(x => x % 2 == 0)
}
Según la propiedad Scala val, no puede asignar otra función a even2, la misma regla que sq.
¿Por qué llamar a la función eval2 val no imprime "val" una y otra vez?
Código de origen:
val even2: (Int => Boolean) = {
println("val");
(x => x % 2 == 0)
}
Sabemos que en Scala la última declaración del tipo de expresión anterior (dentro de {..}) es en realidad volver al lado izquierdo. Por lo tanto, termina configurando even2 en la función "x => x% 2 == 0", que coincide con el tipo que declaró para el tipo even2 val, es decir (Int => Boolean), por lo que el compilador está contento. Ahora even2 solo apunta a la función "(x => x% 2 == 0)" (no cualquier otra instrucción anterior, es decir, println ("val") etc. Invocar event2 con diferentes parámetros invocará realmente "(x => x% 2) == 0) "código, ya que solo eso se guarda con event2.
scala> even2(2)
res7: Boolean = true
scala> even2(3)
res8: Boolean = false
Solo para aclarar esto más, la siguiente es una versión diferente del código.
scala> val even2: (Int => Boolean) = {
| println("val");
| (x => {
| println("inside final fn")
| x % 2 == 0
| })
| }
Lo que sucederá ? aquí vemos "interior fn final" impreso una y otra vez, cuando llamas a even2 ().
scala> even2(3)
inside final fn
res9: Boolean = false
scala> even2(2)
inside final fn
res10: Boolean = true
scala>
Ejecutar una definición como def x = e
no evaluará la expresión e. En su lugar, e se evalúa cada vez que se invoca x.
Alternativamente, Scala ofrece una definición de valor
val x = e
, que evalúa el lado derecho como parte de la evaluación de la definición. Si x se usa posteriormente, se reemplaza inmediatamente por el valor precalculado de e, de modo que la expresión no necesita ser evaluada nuevamente.
Además, Val es una evaluación por valor. Lo que significa que la expresión del lado derecho se evalúa durante la definición. Donde Def es por evaluación de nombre. No evaluará hasta que se use.
Además de las respuestas útiles anteriores, mis hallazgos son:
def test1: Int => Int = {
x => x
}
--test1: test1[] => Int => Int
def test2(): Int => Int = {
x => x+1
}
--test2: test2[]() => Int => Int
def test3(): Int = 4
--test3: test3[]() => Int
Lo anterior muestra que "def" es un método (con parámetros de argumento cero) que devuelve otra función "Int => Int" cuando se invoca.
La conversión de métodos a funciones se explica aquí: https://tpolecat.github.io/2014/06/09/methods-functions.html
En REPL,
scala> def even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean
scala> val even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean = $$Lambda$1157/1017502292@57a0aeb8
def significa call-by-name
, evaluado bajo demanda
val significa call-by-value
, evaluado durante la inicialización
Int => Boolean
significa Creo que la sintaxis definida esdef foo(bar: Baz): Bin = expr