Diferencias entre estas tres formas de definir una función en Scala

92

Dadas tres formas de expresar la misma función f(a) := a + 1:

val f1 = (a:Int) => a + 1
def f2 = (a:Int) => a + 1
def f3:(Int => Int) = a => a + 1

¿En qué se diferencian estas definiciones? El REPL no indica diferencias obvias:

scala> f1
res38: (Int) => Int = <function1>
scala> f2
res39: (Int) => Int = <function1>
scala> f3
res40: (Int) => Int = <function1>
qrest
fuente
11
Debe tener en cuenta que en el segundo bloque anterior, la evaluación f1en el REPL muestra el valor vinculado estáticamente al f1evaluar f2y f3muestra el resultado de invocar esos métodos. En particular, Function1[Int, Int]se produce una nueva instancia cada vez que se invoca f2o f3, mientras que f1es la misma Function1[Int, Int]para siempre.
Randall Schulz
@RandallSchulz dado que la versión val no requiere una nueva instancia de función, ¿por qué se usaría def en este caso?
virtualeyes
2
@virtualeyes La única situación que puedo recordar donde uno ve defs produciendo valores FunctionN [...] es en la biblioteca del analizador combinador. No es muy común escribir métodos que produzcan funciones y virtualmente nunca se usaría una def para producir muchas copias de una función que no cambia semántica / funcionalmente.
Randall Schulz

Respuestas:

112

f1 es una función que toma un número entero y devuelve un número entero.

f2es un método con aridad cero que devuelve una función que toma un número entero y devuelve un número entero. (Cuando escribe f2en REPL más tarde, se convierte en una llamada al método f2).

f3es lo mismo que f2. Simplemente no está empleando la inferencia de tipos allí.

desaparecido faktor
fuente
6
¿Por qué f1es un functiony f2es un method?
Freewind
17
@Freewind, una función es un objeto con un método llamado apply. Un método, bueno, es un método.
missingfaktor
Respuesta impresionante. Pregunta: dices que f2 tiene aridad cero, pero ¿no es unario? en.wikipedia.org/wiki/Arity "Una función nulary no toma argumentos. Una función unaria toma un argumento." ¡Sólo curioso!
Matthew Cornell
5
@MatthewCornell, en f2sí mismo no acepta argumentos. El objeto de función que devuelve lo hace.
missingfaktor
122

Dentro de una clase, valse evalúa en la inicialización, mientras defque solo se evalúa cuando y cada vez que se llama a la función. En el código siguiente, verá que x se evalúa la primera vez que se usa el objeto, pero no cuando se accede al miembro x. Por el contrario, y no se evalúa cuando se crea una instancia del objeto, sino que se evalúa cada vez que se accede al miembro.

  class A(a: Int) {
    val x = { println("x is set to something"); a }
    def y = { println("y is set to something"); a }
  }

  // Prints: x is set to something
  val a = new A(1)

  // Prints: "1"
  println(a.x)

  // Prints: "1"                               
  println(a.x)

  // Prints: "y is set to something" and "1"                                  
  println(a.y)

  // Prints: "y is set to something" and "1"                                                                                   
  println(a.y)
Jack
fuente
@JacobusR, ¿esto es cierto solo dentro de una clase?
Andrew Cassidy
por ejemplo: scala> var b = 5 b: Int = 5 scala> val a: (Int => Int) = x => x + ba: Int => Int = <function1> scala> a (5) res48: Int = 10 scala> b = 6 b: Int = 6 scala> a (5) res49: Int = 11 Esperaba que a (5) devolviera 10 y que el valor de b estuviera en línea
Andrew Cassidy
@AndrewCassidy la función aes inmutable y se evalúa en la inicialización, pero bsigue siendo un valor mutable. Por tanto, la referencia a bse establece durante la inicialización, pero el valor almacenado por bpermanece mutable. Para divertirse, ahora puede crear uno nuevo val b = 123. Después de esto a(5), siempre dará 11, ya bque ahora es un valor completamente nuevo.
Jack
@JacobusR gracias ... esto tiene sentido. Esto coincide con la definición de "alcance léxico" ya que la función a lleva una referencia a la "var b" original. Supongo que lo que me confundió es que dicen: var b = 5; val c = b; b = 6; actúa de manera diferente. Supongo que no debería esperar que una definición de función que lleve referencias al alcance "léxico" original se comporte de la misma manera que un Int.
Andrew Cassidy
3

La ejecución de una definición como def x = e no evaluará la expresión e . En su lugar, e se evalúa siempre que se usa x . Alternativamente, Scala ofrece una definición de valor val x = e , que evalúa el lado derecho e como parte de la evaluación de la definición. Si luego se usa x posteriormente, se reemplaza inmediatamente por el valor precalculado de e , por lo que la expresión no necesita ser evaluada nuevamente.

Scala por ejemplo de Martin Odersky

Alejandro
fuente