learn - Los argumentos perezosos de Scala: ¿Cómo funcionan?
scala learn (2)
El artículo de Wikipedia para Scala incluso responde lo que hace la palabra clave lazy
:
El uso de la palabra clave perezosa difiere la inicialización de un valor hasta que se use este valor.
Además, lo que tienes en este ejemplo de código con q : => Parser[U]
es un parámetro de llamada por nombre. Un parámetro declarado de esta manera permanece sin evaluar, hasta que lo evalúe explícitamente en algún lugar de su método.
Aquí hay un ejemplo de Scala REPL sobre cómo funcionan los parámetros de llamada por nombre:
scala> def f(p: => Int, eval : Boolean) = if (eval) println(p)
f: (p: => Int, eval: Boolean)Unit
scala> f(3, true)
3
scala> f(3/0, false)
scala> f(3/0, true)
java.lang.ArithmeticException: / by zero
at $anonfun$1.apply$mcI$sp(<console>:9)
...
Como puede ver, el 3/0
no se evalúa en absoluto en la segunda llamada. La combinación del valor diferido con un parámetro de llamada por nombre como el anterior da como resultado el siguiente significado: el parámetro q
no se evalúa inmediatamente al llamar al método. En cambio, se asigna al valor perezoso p
, que tampoco se evalúa inmediatamente. Solo después, cuando se usa p
esto conduce a la evaluación de q
. Pero, como p
es un val
el parámetro q
solo se evaluará una vez y el resultado se almacenará en p
para su posterior reutilización en el ciclo.
Puede ver fácilmente en el repl, que la evaluación múltiple puede ocurrir de otra manera:
scala> def g(p: => Int) = println(p + p)
g: (p: => Int)Unit
scala> def calc = { println("evaluating") ; 10 }
calc: Int
scala> g(calc)
evaluating
evaluating
20
En el archivo Parsers.scala (Scala 2.9.1) de la biblioteca de los combinadores de analizadores, parece que encontré una característica de Scala menos conocida llamada "argumentos vagos". Aquí hay un ejemplo:
def ~ [U](q: => Parser[U]): Parser[~[T, U]] = { lazy val p = q // lazy argument
(for(a <- this; b <- p) yield new ~(a,b)).named("~")
}
Aparentemente, está sucediendo algo aquí con la asignación del argumento call-name q
al perezoso val p
.
Hasta ahora no he podido averiguar qué hace esto y por qué es útil. ¿Alguien puede ayudar?
Los argumentos de llamada por nombre se invocan cada vez que los solicite . Lazy vals se llaman la primera vez y luego el valor se almacena. Si vuelve a solicitarlo, obtendrá el valor almacenado.
Por lo tanto, un patrón como
def foo(x: => Expensive) = {
lazy val cache = x
/* do lots of stuff with cache */
}
es el último modelo de posponer-trabajar-tan-largo-como-es-y-solo-hacer-eso-una vez. Si su ruta de código nunca lo lleva a necesitar x
en absoluto, nunca será evaluado. Si lo necesita varias veces, solo se evaluará una vez y se almacenará para usarlo en el futuro. Entonces, usted hace la costosa llamada ya sea cero (si es posible) o una (si no) veces, garantizado.