2012-03-21 11 views
35

En el archivo Parsers.scala (Scala 2.9.1) de la biblioteca de los combinadores del analizador, parece que encontré una característica de Scala menos conocida llamada "argumentos vagos". He aquí un ejemplo:Los argumentos perezosos de Scala: ¿Cómo funcionan?

def ~ [U](q: => Parser[U]): Parser[~[T, U]] = { lazy val p = q // lazy argument 
    (for(a <- this; b <- p) yield new ~(a,b)).named("~") 
} 

Al parecer, hay algo pasando aquí con la asignación de los argumentos de llamada por nombre q a la val perezoso p.

Hasta ahora no he podido averiguar qué hace esto y por qué es útil. ¿Alguien puede ayudar?

+1

¿Ha poner ningún esfuerzo en tratar de encontrar tú mismo? Es parte del lenguaje Scala y una búsqueda en Internet debería revelar suficientes hits en 'scala lazy'. – ziggystar

+1

@ziggystar: Ya hice 2-3 búsquedas de Google y no encontré nada útil. Se mencionaron argumentos perezosos en alguna solicitud de función de Scala, pero no se dio ninguna explicación de la que pudiera tener sentido. –

+1

@ziggystar: la solicitud de función está aquí: https://issues.scala-lang.org/browse/SI-240. Además, la búsqueda de 'scala lazy' o incluso' scala lazy argument' no parece proporcionar mucha información útil porque en su mayoría obtendrás resultados sobre las cosas más básicas como lazy val's y call-by-name. –

Respuesta

76

Los argumentos de llamada por nombre se llaman cada vez que los solicita. Lazy vals se llama la primera vez y luego se almacena el valor. 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 patrón de put-off-trabajo-como-mucho-como-posible-y-solamente-do-it-una vez última. Si la ruta de su código nunca lo lleva a necesitar x, entonces 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.

+1

+1, había supuesto que llamar por un nombre era en parte igual a perezoso, ¡pero no en todos los casos! Gracias por la aclaración ... – virtualeyes

+0

aka llamada por necesidad – JPC

20

artículo de Wikipedia para Scala siquiera responde a lo que hace la palabra clave lazy:

El uso de la palabra clave perezoso aplaza la inicialización de un valor hasta que se utiliza 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í se muestra un ejemplo de la REPL Scala 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 se puede ver, el 3/0 no se evalúan 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 su lugar, se asigna al valor 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 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.

Se puede ver fácilmente en el repl, que la evaluación múltiple puede suceder lo contrario:

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 
Cuestiones relacionadas