Ленивые аргументы Скалы: как они работают? - PullRequest
36 голосов
/ 21 марта 2012

В файле Parsers.scala (Scala 2.9.1) из библиотеки комбинаторов синтаксического анализа я, кажется, натолкнулся на менее известную функцию Scala, называемую «ленивые аргументы».Вот пример:

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

По-видимому, здесь что-то происходит с присвоением аргумента call-by-name q ленивому val p.

Пока чтоЯ не смог понять, что это делает и почему это полезно.Кто-нибудь может помочь?

Ответы [ 2 ]

83 голосов
/ 21 марта 2012

Аргументы для вызова по имени вызываются каждый раз, когда вы их запрашиваете .Ленивые значения называются в первый раз , а затем значение сохраняется.Если вы запросите его снова, вы получите сохраненное значение.

Таким образом, шаблон, подобный

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

, является окончательным отложением работы до тех пор, покашаблон "возможно и только сделай это один раз".Если ваш путь к коду никогда не потребует x, он никогда не будет оценен.Если вам это нужно несколько раз, он будет оценен только один раз и сохранен для будущего использования.Таким образом, вы делаете дорогой вызов либо ноль (если возможно), либо один (если нет) раз, гарантировано.

22 голосов
/ 21 марта 2012

Статья в Википедии для Scala даже отвечает на то, что делает ключевое слово lazy:

Использование ключевого слова lazy откладывает инициализацию значения до тех пор, пока это значение не будет использовано.

Кроме того, в этом примере кода с q : => Parser[U] имеется параметр call-by-name.Параметр, объявленный таким образом, остается неоцененным, пока вы явно не оцените его где-нибудь в своем методе.

Вот пример из REPL scala о том, как работают параметры вызова по имени:

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

Как видите, 3/0 вообще не оценивается во втором вызове.Объединение ленивого значения с параметром call-by-name, как указано выше, приводит к следующему значению: параметр q не оценивается сразу при вызове метода.Вместо этого ему присваивается значение lazy p, которое также не оценивается сразу.Только позже, когда используется p, это приводит к оценке q.Но, поскольку p является val, параметр q будет оцениваться только один раз , а результат сохраняется в p для последующего повторного использования в цикле.

YouВ репле легко увидеть, что множественная оценка может произойти иначе:

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