Динамически меняющиеся функции scala
Я изучаю Scala, и я наткнулся на следующий код.
def whileLoop(cond: => Boolean)(body: => Unit): Unit =
if (cond) {
body
whileLoop(cond)(body)
}
var i = 10
whileLoop (i > 0) {
println(i)
i -= 1
}
Выходные числа от 10 до 1.
Таким образом, и cond, и body являются параметрами "call by name". Это означает, что они оцениваются при использовании в функции. Если я правильно понимаю. Я не понимаю, как тело
println(i)
i -= 1
Изменения для каждого уровня рекурсии, который применяется, тело изменяется по мере изменения переменной i. Но как это работает? Каждый раз, когда передается одно и то же тело функции, для меня эта функция остается прежней, но запуск программы показывает мне обратное. Я знаю, что функция оценивается каждый раз, но я не понимаю, как переменная i внутри изменяется каждый раз, поэтому кто-то может объяснить мне, как это работает?
3 ответа
В этом примере тело
println(i)
i -= 1
это замыкание, которое работает с переменной i
который находится в рамках определения тела. следовательно i
не является локальной переменной тела, это означает, что операция -=
изменяет только существующее значение i
, а не локальная копия, которая отбрасывается после вызова метода.
То же самое верно для условия: это замыкание, которое захватывает ту же самую переменную i
следовательно, после каждого выполнения тела условие будет видеть обновленное значение i
,
Давайте немного перепишем пример без изменения значения: во-первых, мы можем переписать whileLoop
принимать функции в качестве аргументов вместо параметров по имени:
def whileLoop(cond: () => Boolean)(body: () => Unit): Unit =
if (cond()) {
body()
whileLoop(cond)(body)
}
Это переписано whileLoop
семантически идентичен, поскольку аргумент call-by-name передается как выражение, а не как оценка выражения. Отказ от ответственности: я не знаю, есть ли технические различия, например, в отношении производительности.
Во-вторых, мы можем сделать выражения, которые передаются для cond
а также body
функции, которые не имеют аргументов:
val condDef = () => i > 0
val bodyDef = () => {
println(i)
i -= 1
}
Поскольку оба они ссылаются на переменную i
которые не являются частью их параметров и не определены внутри их тела, мы должны поставить i
в их объеме.
def main(args: Array[String]) {
var i = 10
val condDef = () => i > 0
val bodyDef = () => {
println(i)
i -= 1
}
whileLoop (condDef) {
bodyDef
}
}
Так i
доступен из обоих, condDef
а также bodyDef
и доступен и изменен, когда они оценены.
i -= 1 берет переменную i и переназначает ее на значение, уменьшенное на 1. Ваше тело ссылается на одну и ту же переменную i, которая изменяется при каждом вызове тела. Игнорируя всю рекурсию и ваш whileLoop, по сути, он делает это:
var i = 10
println(i) // prints 10
i -= 1
println(i) // prints 9
i -= 1
...
i -= 1
println(i) // prints 1
i -= 1
println(i) // prints 0
Когда вы объявляете параметр с типом => Type
вы объявляете этот параметр как анонимную функцию (функцию, которая только возвращает Type
без какого-либо участия). Поэтому, когда функция вызывается в первый раз, каждый параметр оценивается для этого конкретного значения i
каждый раз. Как body
изменяет i
значение с каждой итерации, программа будет переоценивать i
каждый раз body
меняет это.
Я знаю, это звучит сложно, но терпите меня. Посмотрим, что произойдет, когда вы удалите =>
,
Если вы удалите =>
Вы не объявляете анонимные функции для переоценки. Вы определяете параметры, которые невозможно переписать. И так как условие не может быть переоценено каждый раз, у вас будет бесконечный булл.
Я надеюсь, что это объяснение поможет.