Яка різниця між:
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
.
even
викликається.
def
можна використовувати для визначення методу, і це найшвидший варіант. @ A.Karimi
even eq even
.
@inline
атрибут . Але він не може вбудовувати функції, оскільки виклик функції - це виклик віртуальному apply
методу об'єкта функції. JVM може девіалізувати та вбудовувати такі дзвінки в деяких ситуаціях, але не в цілому.
Врахуйте це:
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
even2
два рази, один раз із 1
та один раз 2
. Ви отримаєте різні відповіді на кожен дзвінок. Таким чином, хоча println
при наступних викликах функція не виконується, ви не отримуєте однакового результату від різних дзвінків even2
. Щодо того, чому println
це не виконується знову, це вже інше питання.
Дивіться це:
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 - це виклик функції. Він буде оцінювати кожен раз, коли він викликається.
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>
Виконання такого визначення, def x = e
яке не буде оцінювати вираз e. Замість е оцінюється кожен раз, коли викликається х.
Крім того, Scala пропонує визначення значення
val x = e
, яке оцінює праву частину як частину оцінки визначення. Якщо x потім використовується згодом, його негайно замінюють заздалегідь обчисленим значенням e, так що вираз не потрібно оцінювати знову.
також Val - це оцінка за вартістю. Що означає вираз правого боку оцінюється під час визначення. Де Деф - за оцінкою імен. Він не буде оцінювати, поки він не буде використаний.
Крім вищезгаданих корисних відповідей, мої висновки:
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/1017502292@57a0aeb8
def засоби call-by-name
, що оцінюються на вимогу
засоби val call-by-value
, оцінені під час ініціалізації
Int => Boolean
означає? Я думаю, що синтаксис визначення єdef foo(bar: Baz): Bin = expr