В чем разница между "def" и " val " для определения функции


в чем разница между:

def even: Int => Boolean = _ % 2 == 0

и

val even: Int => Boolean = _ % 2 == 0

как можно назвать как even(10).

7 185

7 ответов:

метод 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 в строке 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 is инициализировано, значение, возвращаемое 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 последнее утверждение вышеприведенного вида выражения (inside { .. }) фактически возвращается в левую сторону. Таким образом, вы в конечном итоге устанавливаете even2 в функцию "x => x % 2 == 0", которая соответствует типу, объявленному для типа even2 val, т. е. (Int => Boolean), поэтому компилятор счастлив. Теперь even2 указывает только на функцию" (x => x % 2 == 0)"(не любое другое утверждение перед т. е. println ("val") и т. д. Вызов event2 с различными параметрами будет фактически вызывать код" (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
     |             })
     |        }

что будет ? здесь мы видим, что "inside final 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. вместо 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