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


214

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

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

и

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

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


Привет, что Int => Booleanзначит? Я думаю, что синтаксис определенияdef foo(bar: Baz): Bin = expr
Ziu

@Ziu означает, что функция 'even' получает Int в качестве аргумента и возвращает логическое значение в качестве типа значения. Таким образом, вы можете назвать 'even (3)', который оценивает как логическое значение 'false'
Денис Лобур,

@DenysLobur спасибо за ваш ответ! Любая ссылка об этом синтаксисе?
Зиу

@Ziu Я в основном узнал об этом из курса Coursera Одерского - coursera.org/learn/progfun1 . К тому времени, когда вы закончите, вы поймете, что означает «Type => Type»
Денис Лобур

Ответы:


326

Метод 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, так что вы можете определить его, даже если вы не будете использовать его в некоторых ветвях.

С помощью a lazy valвы получите ленивую оценку: вы можете определить ее, даже если вы не будете использовать ее в некоторых ветвях, и она будет оцениваться один или никогда, но вы получите небольшие издержки от двойной проверки блокировки при каждом доступе к вашему lazy val.

Как заметил @SargeBorsch, вы можете определить метод, и это самый быстрый вариант:

def even(i: Int): Boolean = i % 2 == 0

Но если вам нужна функция (не метод) для композиции функций или для функций более высокого порядка (например filter(even)), компилятор будет генерировать функцию из вашего метода каждый раз, когда вы используете ее как функцию, поэтому производительность может быть несколько хуже, чем с val.


Не могли бы вы сравнить их по производительности? Разве не важно оценивать функцию каждый раз, когда evenвызывается.
Амир Карими

2
defможет использоваться для определения метода, и это самый быстрый вариант. @ A.Karimi
Отображаемое имя

2
Для прикола: на 2.12 even eq even.
Сом-Снитт

Есть ли концепция встроенных функций, как в C ++? Я из мира C ++, так что извините за мое невежество.
animageofmine

2
@animageofmine Scala-компилятор может попытаться встроить методы. Есть @inlineатрибут для этого. Но он не может встроить функции, потому что вызов функции - это вызов виртуального applyметода объекта функции. В некоторых ситуациях JVM может девиртуализировать и включать такие вызовы, но не в целом.
сен

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снова вызывает тело метода. Но с even2ie 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

6
Я думаю, что ваше объяснение может подразумевать то, что вы не намерены. Попробуйте позвонить even2дважды, один раз с 1и один раз с 2. Вы получите разные ответы на каждый звонок. Таким образом, хотя при printlnпоследующих вызовах функция не выполняется, вы не получите одинаковый результат от разных вызовов even2. Что касается того, почему printlnне выполняется снова, это другой вопрос.
Мелстон

1
это на самом деле очень интересно. Как и в случае с val, то есть даже с 2, значение val оценивается как параметризованное значение. так что да с val вы оценки функции, ее значение. Println не является частью оцененного значения. Это часть оценки, но не оцененная стоимость. Хитрость здесь в том, что оценочное значение на самом деле является параметризованным значением, которое зависит от некоторого ввода. умная вещь
MaatDeamon

1
@ Melston точно! это то, что я понял, так почему же println не выполняется снова, пока вывод изменяется?
Aur

1
@aur, что возвращает even2, на самом деле является функцией (выражение в скобках в конце определения even2). Эта функция фактически вызывается с параметром, который вы передаете в even2 каждый раз, когда вызываете ее.
Мелстон

5

Посмотри это:

  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> 

1

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

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


0

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


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

В 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, оценивается при инициализации


С таким старым вопросом и с таким количеством уже отправленных ответов часто бывает полезно объяснить, чем ваш ответ отличается или дополняет информацию, представленную в существующих ответах.
JWVH
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.