web-dev-qa-db-fra.com

Arguments paresseux de Scala: Comment fonctionnent-ils?

Dans le fichier Parsers.scala (Scala 2.9.1) de la bibliothèque de combinaisons d'analyseurs, je semble avoir rencontré une fonctionnalité moins connue Scala appelée "arguments paresseux". Voici un exemple:

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

Apparemment, il y a quelque chose qui se passe ici avec l'affectation de l'argument appel-b par nom q sur le VAL paresseux p.

Jusqu'à présent, je n'ai pas pu résoudre ce que cela fait et pourquoi c'est utile. Quelqu'un peut-il aider?

37
python dude

Les arguments appelés par nom sont appelés chaque fois que vous les demandez. Les valys paresseux sont appelés la première fois puis la valeur est stockée. Si vous le demandez à nouveau, vous obtiendrez la valeur stockée.

Ainsi, un motif comme

def foo(x: => Expensive) = {
  lazy val cache = x
  /* do lots of stuff with cache */
}

est le motif ultime de la mise hors service de mise hors œuvre à la même manière possible. Si votre chemin de code ne vous prend jamais besoin de x du tout, il ne sera jamais évalué. Si vous en avez besoin de plusieurs fois, cela ne sera évalué qu'une fois et stocké pour une utilisation future. Donc, vous faites un appel coûteux soit zéro (si possible) ou une fois (sinon) fois, garanti.

91
Rex Kerr

L'article de Wikipedia pour - Scala répond même à ce que le mot clé lazy fait:

L'utilisation du mot-clé paresseux montre l'initialisation d'une valeur jusqu'à ce que cette valeur soit utilisée.

De plus, ce que vous avez dans ce code échantillon avec q : => Parser[U] est un paramètre appelé par nom. Un paramètre déclaré de cette manière reste inévalué jusqu'à ce que vous l'évaluiez explicitement quelque part dans votre méthode.

Voici un exemple de scala =REPL sur la manière dont les paramètres d'appel-b par nom fonctionnent:

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

Comme vous pouvez le constater, le 3/0 ne pas être évalué du tout dans le deuxième appel. Combinant la valeur paresseuse avec un paramètre Call-by-nom, comme ci-dessus, les résultats suivants: le paramètre q n'est pas évalué immédiatement lorsque vous appelez la méthode. Au lieu de cela, il est attribué à la valeur paresseuse p, qui n'est également pas évalué immédiatement. Seul le lachaton, lorsque p est utilisé cela conduit à l'évaluation de q. Mais, comme p est un val le paramètre q _ ne sera évalué que une fois et le Le résultat est stocké dans p pour une réutilisation ultérieure dans la boucle.

Vous pouvez facilement voir dans la réplique, que l'évaluation multiple peut se produire sinon:

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
27
Frank