Яка різниця між "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, це означає, що функція "навіть" отримує Int як аргумент і повертає булевий тип як тип значення. Таким чином, ви можете зателефонувати "even (3)", що оцінює булеву "false"
Denys Lobur

@DenysLobur дякую за вашу відповідь! Якась посилання на цей синтаксис?
Зіу

@Ziu я в основному це з’ясував з курсу « Курси курсу» Одерського - coursera.org/learn/progfun1 . Коли ви закінчите це, ви зрозумієте, що означає "Тип => Тип"
Денис Лобур

Відповіді:


325

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


Чи можете ви порівняти їх стосовно продуктивності? Чи не важливо оцінювати функцію щоразу, коли evenвикликається.
Амір Карімі

2
defможна використовувати для визначення методу, і це найшвидший варіант. @ A.Karimi
Відображення імені

2
Для розваги: ​​2.12 even eq even.
som-snytt

Чи існує поняття вбудованих функцій, як у 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методу. Але з 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

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

1
це насправді дуже цікаво. Це як у випадку val, тобто навіть2, значення 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 (навіть вар) оцінюється негайно і присвоюється.
Тепер змініть 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)" (не будь-яка інша заява перед ie 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
     |             })
     |        }

Що станеться ? тут ми бачимо, що "всередині остаточного 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. Замість е оцінюється кожен раз, коли викликається х.

Крім того, Scala пропонує визначення значення val x = e, яке оцінює праву частину як частину оцінки визначення. Якщо x потім використовується згодом, його негайно замінюють заздалегідь обчисленим значенням e, так що вираз не потрібно оцінювати знову.


0

також Val - це оцінка за вартістю. Що означає вираз правого боку оцінюється під час визначення. Де Деф - за оцінкою імен. Він не буде оцінювати, поки він не буде використаний.


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.