scala - poligono - pairs en r
¿Cuál es la diferencia entre "def" y "val" para definir una función (7)
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)
.
Además de las respuestas útiles anteriores, mis conclusiones 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 cero argumentos) que devuelve otra función "Int => Int" cuando se invoca.
La conversión de métodos a funciones está bien explicada aquí: https://tpolecat.github.io/2014/06/09/methods-functions.html
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 se evaluará hasta que se use.
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 : para cada llamada a even
, llama al cuerpo del método even
nuevamente. Pero con even2
es decir, val , la función se inicializa solo una vez mientras se declara (y por lo tanto imprime val
en la línea 4 y nunca más) y se usa la misma salida cada vez que se accede. Por ejemplo intenta 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 se inicializa x
, el valor devuelto por Random.nextInt
se establece como el valor final de x
. La próxima vez que se vuelva a utilizar x
, siempre devolverá el mismo valor.
También puede 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
La ejecución de una definición como def x = e
no evaluará la expresión e. En lugar de 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 luego se usa x, se reemplaza inmediatamente por el valor precalculado de e, por lo que no es necesario evaluar la expresión nuevamente.
Method def even
evalúa en 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
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 la 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 el val
de val
para múltiples llamadas. Obtendrá el mismo rendimiento con una sola llamada. Y sin llamadas, no tendrá gastos generales desde la def
, por lo que puede definirlo incluso si no lo usará en algunas sucursales.
Con un valor lazy val
obtendrás una evaluación perezosa: puedes definirlo incluso si no lo usarás en algunas sucursales, y se evalúa una vez o nunca, pero obtendrás un poco de sobrecarga por el doble control en cada acceso a tu lazy val
.
Como @SargeBorsch señaló, 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 la función o para funciones de orden superior (como filter(even)
), el compilador generará una función desde su método cada vez que la use como una función, por lo que el rendimiento podría ser un poco peor que con val
.
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 cambia de val a def .. se imprimirá 9! Def es una llamada de función ... evaluará cada vez que se llame.
val es decir "sq" es por definición de Scala es fijo. Se evalúa en el momento de la declaración, no se puede cambiar más adelante. En otros ejemplos, donde even2 también es val, pero se declara con la firma de función, es decir, "(Int => Boolean)", por lo que no es el tipo Int. Es una función y su valor se establece mediante la siguiente expresión
{
println("val");
(x => x % 2 == 0)
}
Según la propiedad val Scala, no puede asignar otra función a even2, la misma regla que sq.
¿Sobre por qué llamar a la función val eval2 no imprimir "val" una y otra vez?
Código original:
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. Así que terminas configurando even2 en la función "x => x% 2 == 0", que coincide con el tipo que declaraste para el tipo de valor de even2, es decir (Int => Boolean), por lo que el compilador está contento. Ahora even2 solo apunta a la función "(x => x% 2 == 0)" (no a ninguna otra declaración antes, es decir, println ("val") etc. Invocar event2 con diferentes parámetros realmente invocará "(x => x% 2 == 0) "código, ya que solo 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 "dentro de fn final" impreso una y otra vez, cuando llama a even2 ().
scala> even2(3)
inside final fn
res9: Boolean = false
scala> even2(2)
inside final fn
res10: Boolean = true
scala>