Відмінності між цими трьома способами визначення функції в Scala


92

Дано три способи вираження тієї самої функції f(a) := a + 1:

val f1 = (a:Int) => a + 1
def f2 = (a:Int) => a + 1
def f3:(Int => Int) = a => a + 1

Чим ці визначення відрізняються? REPL не вказує на очевидні відмінності:

scala> f1
res38: (Int) => Int = <function1>
scala> f2
res39: (Int) => Int = <function1>
scala> f3
res40: (Int) => Int = <function1>

11
Зверніть увагу, що у другому блоці вище, оцінка f1в REPL показує значення, статично прив’язане під f1час оцінки, f2і f3показує результат виклику цих методів. Зокрема, новий Function1[Int, Int]екземпляр створюється кожного разу, f2або коли f3він викликається, але f1залишається незмінним Function1[Int, Int]назавжди.
Randall Schulz

@RandallSchulz, враховуючи, що версія val не вимагає нового екземпляра функції, чому в такому випадку взагалі використовуватиметься def?
virtualeyes

2
@virtualeyes Єдина ситуація, яку я можу згадати, коли хтось бачить defs, що дає значення FunctionN [...], це в бібліотеці аналізатора комбінатора. Не так часто писати методи, які дають функції, і практично ніколи не можна використовувати def, щоб отримати багато копій семантично / функціонально незмінної функції.
Randall Schulz

Відповіді:


112

f1 - це функція, яка приймає ціле число і повертає ціле число.

f2є методом з нульовим значенням arity, який повертає функцію, яка приймає ціле число і повертає ціле число. (Коли ви вводите f2REPL пізніше, це стає викликом методу f2.)

f3це те саме, що f2. Ви просто не використовуєте там умовивід типу.


6
Чому f1це a functionі f2є method?
Вільний вітер

17
@Freewind, функція - це об’єкт із методом з іменем apply. Метод, ну, це метод.
missingfaktor

Чудова відповідь. Запитання: ви говорите, що f2 має нульовий рівень, але чи не одинарний? en.wikipedia.org/wiki/Arity "Нульова функція не приймає аргументів. Одинарна функція приймає один аргумент." Просто цікаво!
Метью Корнелл

5
@MatthewCornell, f2сам не приймає жодних аргументів. Об'єкт функції, який він повертає, робить.
missingfaktor

122

Усередині класу valоцінюється при ініціалізації, тоді defяк оцінюється лише тоді, коли і кожного разу викликається функція. У наведеному нижче коді ви побачите, що x обчислюється при першому використанні об'єкта, але не знову при доступі до елемента x. На відміну від цього, y не обчислюється при екземплярі об’єкта, а обчислюється кожного разу, коли доступ до елемента здійснюється.

  class A(a: Int) {
    val x = { println("x is set to something"); a }
    def y = { println("y is set to something"); a }
  }

  // Prints: x is set to something
  val a = new A(1)

  // Prints: "1"
  println(a.x)

  // Prints: "1"                               
  println(a.x)

  // Prints: "y is set to something" and "1"                                  
  println(a.y)

  // Prints: "y is set to something" and "1"                                                                                   
  println(a.y)

@JacobusR це правда лише всередині класу?
Ендрю Кассіді,

наприклад: scala> var b = 5 b: Int = 5 scala> val a: (Int => Int) = x => x + ba: Int => Int = <function1> scala> a (5) res48: Int = 10 шкала> b = 6 b: Int = 6 шкала> a (5) res49: Int = 11 Я очікував, що a (5) поверне 10, а значення b буде вбудовано
Ендрю Кассіді,

@AndrewCassidy функція aнезмінна і оцінюється при ініціалізації, але bзалишається змінним значенням. Тож посилання на bвстановлюється під час ініціалізації, але значення, що зберігається, bзалишається змінним. Для розваги тепер ви можете створити нову val b = 123. Після цього ви a(5)завжди дасте 11, оскільки bтепер це абсолютно нове значення.
Джек

@JacobusR дякую ... це має сенс. Це збігається з визначенням "лексичний обсяг", оскільки функція a містить посилання на вихідний "var b". Я здогадуюсь, що мене збентежило, це сказати: var b = 5; val c = b; b = 6; діє по-різному. Думаю, я не повинен очікувати, що визначення функції, яке містить посилання на вихідний "лексичний" обсяг, поводиться так само, як Int.
Ендрю Кассіді,

3

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

Шкала на прикладі Мартіна Одерського

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.