В чем разница между:
def even: Int => Boolean = _ % 2 == 0
и
val even: Int => Boolean = _ % 2 == 0
Оба могут быть названы как even(10)
.
В чем разница между:
def even: Int => Boolean = _ % 2 == 0
и
val even: Int => Boolean = _ % 2 == 0
Оба могут быть названы как even(10)
.
Метод def even
оценивает по вызову и каждый раз создает новую функцию (новый экземпляр Function1
).
def even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = false
val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true
С помощью def
вы можете получить новую функцию при каждом вызове:
val test: () => Int = {
val r = util.Random.nextInt
() => r
}
test()
// Int = -1049057402
test()
// Int = -1049057402 - same result
def test: () => Int = {
val r = util.Random.nextInt
() => r
}
test()
// Int = -240885810
test()
// Int = -1002157461 - new result
val
оценивает, когда определено, def
- при вызове:
scala> val even: Int => Boolean = ???
scala.NotImplementedError: an implementation is missing
scala> def even: Int => Boolean = ???
even: Int => Boolean
scala> even
scala.NotImplementedError: an implementation is missing
Обратите внимание, что существует третий вариант: lazy val
.
Он оценивается при вызове в первый раз:
scala> lazy val even: Int => Boolean = ???
even: Int => Boolean = <lazy>
scala> even
scala.NotImplementedError: an implementation is missing
Но каждый раз возвращает тот же результат (в этом случае тот же самый экземпляр FunctionN
):
lazy val even: Int => Boolean = _ % 2 == 0
even eq even
//Boolean = true
lazy val test: () => Int = {
val r = util.Random.nextInt
() => r
}
test()
// Int = -1068569869
test()
// Int = -1068569869 - same result
Производительность
val
оценивается, когда определено.
def
оценивает каждый вызов, поэтому производительность может быть хуже, чем val
для нескольких вызовов. Вы получите одну и ту же производительность с одним вызовом. И без вызовов вы не получите накладных расходов из def
, поэтому вы можете определить его, даже если вы не будете использовать его в некоторых ветвях.
С помощью lazy val
вы получите ленивую оценку: вы можете определить ее, даже если вы не будете использовать ее в некоторых ветвях, и она оценивает один или несколько раз, но вы получите немного накладных расходов из двойной проверки блокировки при каждом доступе к вашему lazy val
.
Как @SargeBorsch отметил, что вы можете определить метод, и это самый быстрый вариант:
def even(i: Int): Boolean = i % 2 == 0
Но если вам нужна функция (не метод) для составления функции или для функций более высокого порядка (например, filter(even)
), то компилятор будет генерировать функцию из вашего метода каждый раз, когда вы используете ее как функцию, поэтому производительность может быть немного хуже чем с val
.
Рассмотрим это:
scala> def even: (Int => Boolean) = {
println("def");
(x => x % 2 == 0)
}
even: Int => Boolean
scala> val even2: (Int => Boolean) = {
println("val");
(x => x % 2 == 0)
}
val //gets printed while declaration. line-4
even2: Int => Boolean = <function1>
scala> even(1)
def
res9: Boolean = false
scala> even2(1)
res10: Boolean = false
Вы видите разницу? Короче говоря:
def: для каждого вызова even
он снова вызывает тело метода even
. Но с even2
то есть val, функция инициализируется только один раз во время объявления (и, следовательно, печатает val
в строке 4 и никогда больше), и тот же вывод используется каждый раз при его доступе. Например, попробуйте сделать это:
scala> import scala.util.Random
import scala.util.Random
scala> val x = { Random.nextInt }
x: Int = -1307706866
scala> x
res0: Int = -1307706866
scala> x
res1: Int = -1307706866
Когда x
инициализируется, значение, возвращаемое Random.nextInt
, устанавливается как окончательное значение x
. В следующий раз x
будет использоваться снова, он всегда будет возвращать то же значение.
Вы также можете лениво инициализировать x
. то есть в первый раз, когда он используется, он инициализируется, а не во время объявления. Например:
scala> lazy val y = { Random.nextInt }
y: Int = <lazy>
scala> y
res4: Int = 323930673
scala> y
res5: Int = 323930673
Смотрите это:
var x = 2 // using var as I need to change it to 3 later
val sq = x*x // evaluates right now
x = 3 // no effect! sq is already evaluated
println(sq)
Удивительно, что это напечатает 4, а не 9! val (четный var) оценивается немедленно и назначается.
Теперь измените val на def.. он напечатает 9! Def - вызов функции. Он будет оценивать каждый раз, когда он вызывается.
val т.е. "sq" по определению Scala является фиксированным. Он оценивается прямо во время объявления, вы не можете изменить позже. В других примерах, где even2 также val, но он объявлен с сигнатурой функции, т.е. "(Int => Boolean)", поэтому это не тип Int. Это функция, значение которой задается следующим выражением
{
println("val");
(x => x % 2 == 0)
}
Согласно свойству Scala val, вы не можете назначить другую функцию even2, такое же правило, как sq.
О том, почему вызов функции eval2 val не выдает "val" снова и снова?
Исходный код:
val even2: (Int => Boolean) = {
println("val");
(x => x % 2 == 0)
}
Мы знаем, что в Scala последнее утверждение вышеприведенного вида выражения (внутри {..}) фактически возвращается в левую часть. Таким образом, вы заканчиваете тем, что устанавливаете even2 в функцию "x => x% 2 == 0", которая соответствует типу, который вы объявили для типа Even2 val, т.е. (Int => Boolean), так что компилятор доволен. Теперь even2 указывает только на функцию "(x => x% 2 == 0)" (а не на любые другие операторы до того, как println ("val") и т.д. Вызов события 2 с другими параметрами фактически вызовет "(x => x% 2"). == 0) "код, так как только он сохраняется с помощью event2.
scala> even2(2)
res7: Boolean = true
scala> even2(3)
res8: Boolean = false
Просто чтобы прояснить это подробнее, ниже приведена другая версия кода.
scala> val even2: (Int => Boolean) = {
| println("val");
| (x => {
| println("inside final fn")
| x % 2 == 0
| })
| }
Что случится? здесь мы видим, что "внутри финального fn" печатается снова и снова, когда вы вызываете even2().
scala> even2(3)
inside final fn
res9: Boolean = false
scala> even2(2)
inside final fn
res10: Boolean = true
scala>
также Val оценивается по значению. Это означает, что выражение правой части оценивается во время определения. Где Def - это оценка имени. Он не будет оценивать, пока не будет использован.
Выполнение определения, такого как def x = e
, не будет оценивать выражение e. In- вместо e вычисляется всякий раз, когда вызывается x.
В качестве альтернативы Scala предлагает определение значения val x = e
, которое оценивает правую часть как часть оценки определения. Если затем затем используется x, он немедленно заменяется предварительно вычисленным значением e, так что выражение не нужно вычислять снова.
В дополнение к вышеупомянутым полезным ответам, мои выводы:
def test1: Int => Int = {
x => x
}
--test1: test1[] => Int => Int
def test2(): Int => Int = {
x => x+1
}
--test2: test2[]() => Int => Int
def test3(): Int = 4
--test3: test3[]() => Int
Выше показано, что "def" - это метод (с нулевыми параметрами аргумента), который возвращает другую функцию "Int => Int" при вызове.
Преобразование методов в функции хорошо объяснено здесь: https://tpolecat.github.io/2014/06/09/methods-functions.html
В REPL,
scala> def even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean
scala> val even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean = $$Lambda$1157/[email protected]
def означает call-by-name
, оценивается по требованию
val означает call-by-value
, оцененный во время инициализации