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

185

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

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

и

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

Оба могут быть названы как even(10).

  • 0
    Привет, что значит Int => Boolean ? Я думаю, что синтаксис определения является def foo(bar: Baz): Bin = expr
  • 0
    @Ziu означает, что функция 'even' получает Int в качестве аргумента и возвращает логическое значение в качестве типа значения. Таким образом, вы можете вызвать 'even (3)', который оценивает как Boolean 'false'
Показать ещё 3 комментария
Теги:

8 ответов

284
Лучший ответ

Метод 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.

  • 0
    Не могли бы вы сравнить их по производительности? Разве не важно оценивать функцию каждый раз, когда even вызывается.
  • 2
    def может использоваться для определения метода, и это самый быстрый вариант. @ A.Karimi
Показать ещё 8 комментариев
24

Рассмотрим это:

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
  • 5
    Я думаю, что ваше объяснение может подразумевать то, что вы не намерены. Попробуйте вызвать even2 дважды, один раз с 1 и один раз с 2 . Вы получите разные ответы на каждый звонок. Таким образом, хотя println не выполняется при последующих вызовах, вы не получите тот же результат от разных вызовов even2 . Относительно того, почему println не выполняется снова, это другой вопрос.
  • 1
    это на самом деле очень интересно. Как и в случае с val, то есть даже с 2, значение val оценивается как параметризованное значение. так что да с val вы оценки функции, ее значение. Println не является частью оцененного значения. Это часть оценки, но не оцененная стоимость. Хитрость в том, что оценочное значение на самом деле является параметризованным значением, которое зависит от некоторого ввода. умная вещь
Показать ещё 2 комментария
3

Смотрите это:

  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 - вызов функции. Он будет оценивать каждый раз, когда он вызывается.

1

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

В REPL,

scala> def even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean

scala> val even: Int => Boolean = { _% 2 == 0 }
even: Int => Boolean = $$Lambda$1157/1017502292@57a0aeb8

def означает call-by-name, оценивается по требованию

val означает call-by-value, оцененный во время инициализации

  • 0
    С таким старым вопросом и с таким количеством уже представленных ответов часто бывает полезно объяснить, чем ваш ответ отличается или дополняет информацию, представленную в существующих ответах.
0

В дополнение к вышеупомянутым полезным ответам, мои выводы:

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

0

Выполнение определения, такого как def x = e, не будет оценивать выражение e. In- вместо e вычисляется всякий раз, когда вызывается x.

В качестве альтернативы Scala предлагает определение значения val x = e, которое оценивает правую часть как часть оценки определения. Если затем затем используется x, он немедленно заменяется предварительно вычисленным значением e, так что выражение не нужно вычислять снова.

0

также Val оценивается по значению. Это означает, что выражение правой части оценивается во время определения. Где Def - это оценка имени. Он не будет оценивать, пока не будет использован.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню