¿Cuál es la diferencia entre "def" y "val" para definir una función

214

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).

Amir Karimi
fuente
Hola que Int => Booleansignifica Creo que la sintaxis definida esdef foo(bar: Baz): Bin = expr
Ziu
@Ziu eso significa que la función 'par' recibe un Int como argumento y devuelve un Booleano como un tipo de valor. Entonces puede llamar 'even (3)' que se evalúa como booleano 'false'
Denys Lobur
@DenysLobur gracias por tu respuesta! ¿Alguna referencia sobre esta sintaxis?
Ziu
@Ziu Básicamente lo descubrí en el curso Coursera de Odersky : coursera.org/learn/progfun1 . Cuando lo termines, entenderás lo que significa 'Tipo => Tipo'
Denys Lobur

Respuestas:

325

El método def evenevalú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 defusted 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

valevalú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.

defevalúa en cada llamada, por lo que el rendimiento podría ser peor que valpara 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 valobtendrá 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.

senia
fuente
¿Podría compararlos con respecto al rendimiento? ¿No es importante evaluar la función cada vez que evense llama?
Amir Karimi
2
defse puede usar para definir un método, y esta es la opción más rápida. @ A.Karimi
Nombre para mostrar
2
Para la diversión: en 2.12, even eq even.
som-snytt
¿Existe un concepto de funciones en línea como en c ++? Vengo del mundo c ++, así que perdón por mi ignorancia.
animageofmine
2
El compilador @animageofmine Scala puede intentar incorporar métodos. Hay un @inlineatributo para esto. Pero no puede incluir funciones en línea porque la llamada de función es una llamada al applymétodo virtual de un objeto de función. JVM podría desvirtualizar e incorporar tales llamadas en algunas situaciones, pero no en general.
senia
24

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 evenmétodo. Pero con even2ie val , la función se inicializa solo una vez durante la declaración (y, por lo tanto, se imprime valen 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 xse inicializa, el valor devuelto por Random.nextIntse establece como el valor final de x. La próxima vez que xse 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
Jatin
fuente
66
Creo que su explicación puede implicar algo que no tiene intención. Intenta llamar even2dos veces, una con 1y otra con 2. Obtendrá diferentes respuestas en cada llamada. Entonces, aunque printlnno se ejecuta en llamadas posteriores, no obtiene el mismo resultado de diferentes llamadas a even2. En cuanto a por qué printlnno se ejecuta nuevamente, esa es una pregunta diferente.
melston
1
eso es realmente muy interesante Es como en el caso de val, es decir, even2, el val se evalúa a un valor parametrizado. entonces sí, con un val usted la evaluación de la función, su valor. El println no es parte del valor evaluado. Es parte de la evaluación pero no el valor evaluado. El truco aquí es que el valor evaluado es en realidad un valor parametarizado, que depende de alguna entrada. cosa inteligente
MaatDeamon
1
@melston exactamente! eso es lo que entendí, entonces, ¿por qué no se vuelve a ejecutar println mientras cambia la salida?
aur
1
@aur lo que devuelve even2 es en realidad una función (la expresión entre paréntesis al final de la definición de even2). Esa función se llama realmente con el parámetro que pasa a even2 cada vez que lo invoca.
melston 01 de
5

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.

Apurva Singh
fuente
1

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> 
Sandi
fuente
1

Ejecutar una definición como def x = eno 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.

Gaurav Khare
fuente
0

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.

Sandipan Ghosh
fuente
0

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

prateek
fuente
0

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

GraceMeng
fuente
Con una pregunta tan antigua, y con tantas respuestas ya enviadas, a menudo es útil explicar cómo su respuesta difiere o se agrega a la información proporcionada en las respuestas existentes.
jwvh