Я читав функції Scala (частина чергового туру Скали ). У цій посаді він заявив:
Методи та функції - це не одне і те ж
Але він нічого про це не пояснив. Що він намагався сказати?
Я читав функції Scala (частина чергового туру Скали ). У цій посаді він заявив:
Методи та функції - це не одне і те ж
Але він нічого про це не пояснив. Що він намагався сказати?
Відповіді:
Джим розкрив це в своєму дописі на блозі , але я розміщую тут брифінг для довідки.
Спочатку давайте подивимось, що нам розповідає Специфікація Scala. Глава 3 (типи) розповість нам про типи функцій (3.2.9) та типи методів (3.3.1). У розділі 4 (основні декларації) йдеться про декларацію про значення та визначення (4.1), змінну декларацію та визначення (4.2) та декларації про функції та визначення (4.6). Розділ 6 (вирази) говорить про анонімні функції (6.23) та методичні значення (6.7). Цікаво, що про значення функцій говорять один раз на 3.2.9, а більше ніде.
Тип Функція являє собою (приблизно) тип форми (T1, ..., Tn) => U , яке є скороченням для ознаки FunctionN
в стандартній бібліотеці. Анонімні функції та значення методів мають типи функцій, а типи функцій можуть використовуватися як частина оголошень значень, змінних та функцій та визначень. Фактично це може бути частиною типу методу.
Тип Метод являє собою тип , НЕ значення . Це означає, що для типу методу немає значення - ні об'єкта, ні екземпляра. Як було сказано вище, значення методу насправді має тип функції . Тип методу - це def
декларація - все, def
окрім її тіла.
Декларації та визначення значень та Змінні декларації та визначення є val
і var
деклараціями, включаючи як тип, так і значення - які можуть бути відповідно Тип функцій та Анонімні функції або Методичні значення . Зауважте, що в JVM ці (значення методу) реалізовані з тим, що Java називає "методами".
Функція декларації є def
декларація, в тому числі типу і тіла . Частина типу - це метод типу, а тіло - вираз або блок . Це також реалізовано на JVM з тим, що Java називає "методами".
Нарешті, анонімна функція - це екземпляр типу функції (тобто екземпляр ознаки FunctionN
), а значення методу - те саме! Відмінність полягає в тому, що значення методу створюється з методів, або за допомогою постфіксації підкреслення ( m _
це значення методу, що відповідає "оголошенню функції" ( def
) m
), або процесом, який називається ета-розширення , який нагадує автоматичну передачу від методу функціонувати.
Ось що кажуть у специфікаціях, тому дозвольте сказати це: ми не використовуємо цю термінологію! Це призводить до занадто великої плутанини між так званим "оголошенням функції" , яке є частиною програми (глава 4 - основні декларації) та "анонімною функцією" , що є виразом, та "типом функції" , тобто ну тип - риса.
Нижче наведена термінологія та використана досвідченими програмістами Scala вносить одну зміну в термінологію специфікації: замість того , щоб заявити про функцію , ми скажемо метод . Або навіть декларація методу. Крім того, ми зазначимо, що оголошення цінностей та оголошення змінних також є методами для практичних цілей.
Отже, враховуючи вищезгадану зміну термінології, ось практичне пояснення розрізнення.
Функція являє собою об'єкт , який включає в себе один з FunctionX
ознак, таких , як Function0
, Function1
, Function2
і т.д. Це може бути в тому числі , PartialFunction
а також, які на самому справі проходить Function1
.
Давайте подивимось підпис типу для однієї з таких ознак:
trait Function2[-T1, -T2, +R] extends AnyRef
Ця ознака має один абстрактний метод (він також має кілька конкретних методів):
def apply(v1: T1, v2: T2): R
І це говорить нам усім, що про це потрібно знати. АФункція має apply
метод , який приймає N параметри типів Т1 , Т2 , ..., TN , і повертає що - то типу R
. Він є контра-варіантом параметрів, які він отримує, і спів-варіантом за результатом.
Ця дисперсія означає, що а Function1[Seq[T], String]
є підтипом Function1[List[T], AnyRef]
. Бути підтипом означає, що його можна використовувати замість нього. Можна легко побачити, що якщо я збираюся зателефонувати f(List(1, 2, 3))
та очікувати AnyRef
повернення, будь-який із двох вищезгаданих типів працював би.
Тепер, у чому схожість методу та функції? Ну, якщо f
це функція і m
є методом, локальним для області, то обидва можна назвати так:
val o1 = f(List(1, 2, 3))
val o2 = m(List(1, 2, 3))
Ці дзвінки насправді різні, тому що перший - це лише синтаксичний цукор. Scala розширює його на:
val o1 = f.apply(List(1, 2, 3))
Звичайно, це виклик методу на об'єкт f
. У функцій є також інші синтаксичні цукру на свою користь: функціональні літерали (фактично два) та (T1, T2) => R
підписи типу. Наприклад:
val f = (l: List[Int]) => l mkString ""
val g: (AnyVal) => String = {
case i: Int => "Int"
case d: Double => "Double"
case o => "Other"
}
Ще одна схожість між методом і функцією полягає в тому, що перший може бути легко перетворений в другий:
val f = m _
Scala розширить це , якщо припустити, що m
тип (List[Int])AnyRef
(Scala 2.7):
val f = new AnyRef with Function1[List[Int], AnyRef] {
def apply(x$1: List[Int]) = this.m(x$1)
}
На Scala 2.8 він фактично використовує AbstractFunction1
клас для зменшення розмірів класів.
Зауважте, що не можна конвертувати навпаки - з функції в метод.
Однак у методів є одна велика перевага (ну, два - вони можуть бути трохи швидшими): вони можуть отримувати параметри типу . Наприклад, в той час як f
вище може обов'язково вказати тип List
його отримання ( List[Int]
у прикладі),m
можна параметризувати його:
def m[T](l: List[T]): String = l mkString ""
Я думаю, що це в значній мірі охоплює все, але я радий доповнити це відповідями на будь-які питання, які можуть залишитися.
val f = m
компілятором, як val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }
слід зазначити, що this
внутрішній apply
метод стосується не AnyRef
об'єкта, а об'єкта, метод val f = m _
якого оцінюється ( зовнішній this
, так би мовити) ), оскільки this
є серед значень, які фіксуються замиканням (наприклад, return
як зазначено нижче).
Одна велика практична відмінність методу від функції - це те, що return
означає. return
повертається лише з методу. Наприклад:
scala> val f = () => { return "test" }
<console>:4: error: return outside method definition
val f = () => { return "test" }
^
Повернення з функції, визначеної у методі, робить не локальне повернення:
scala> def f: String = {
| val g = () => { return "test" }
| g()
| "not this"
| }
f: String
scala> f
res4: String = test
Тоді як повернення з локального методу повертається лише з цього методу.
scala> def f2: String = {
| def g(): String = { return "test" }
| g()
| "is this"
| }
f2: String
scala> f2
res5: String = is this
for (a <- List(1, 2, 3)) { return ... }
? Це призупиняється до закриття.
return
повернення значення з функції, а також деякі форм escape
або break
або continue
повертати з методів.
функція Функцію можна викликати зі списком аргументів для отримання результату. Функція містить список параметрів, тіло та тип результату. Функції, що входять до об'єкта класу, ознаки або однотонного, називаються методами . Функції, визначені всередині інших функцій, називаються локальними функціями. Функції з результатом типу Unit називаються процедурами. Анонімні функції у вихідному коді називаються функціональними літералами. Під час виконання функціональні літерали інстанціюються в об'єкти, які називаються функціональними значеннями.
Програмування у Scala Second Edition. Мартін Одерський - Лекс Ложка - Білл Веннерс
Скажімо, у вас є список
scala> val x =List.range(10,20)
x: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
Визначте метод
scala> def m1(i:Int)=i+2
m1: (i: Int)Int
Визначте функцію
scala> (i:Int)=>i+2
res0: Int => Int = <function1>
scala> x.map((x)=>x+2)
res2: List[Int] = List(12, 13, 14, 15, 16, 17, 18, 19, 20, 21)
Метод прийняття аргументу
scala> m1(2)
res3: Int = 4
Визначення функції з val
scala> val p =(i:Int)=>i+2
p: Int => Int = <function1>
Аргумент функції необов’язковий
scala> p(2)
res4: Int = 4
scala> p
res5: Int => Int = <function1>
Аргумент методу є обов'язковим
scala> m1
<console>:9: error: missing arguments for method m1;
follow this method with `_' if you want to treat it as a partially applied function
Перевірте наступний підручник, який пояснює передачу інших відмінностей на прикладах, як інший приклад розходження з Методом Vs Функція, Використання функції як змінних, створення функції, яка повертає функцію
Існує гарна стаття тут , з яких більшість моїх описів приймаються. Лише коротке порівняння функцій та методів щодо мого розуміння. Сподіваюся, це допомагає:
Функції : Вони в основному є об'єктом. Точніше, функції - це об'єкти із застосуванням методу; Тому вони трохи повільніше, ніж методи через їхні накладні витрати. Це аналогічно статичним методам в тому сенсі, що вони не залежать від об'єкта, на який слід викликати. Простий приклад функції так само, як нижче:
val f1 = (x: Int) => x + x
f1(2) // 4
Рядок вище - це не що інше, як присвоєння одного об'єкта іншому, як object1 = object2. Насправді об'єкт2 у нашому прикладі є анонімною функцією, і ліва сторона отримує тип об’єкта через це. Тому тепер f1 є об'єктом (Функція). Анонімна функція - це фактично екземпляр Function1 [Int, Int], який означає функцію з 1 параметром типу Int і зворотним значенням типу Int. Виклик f1 без аргументів дасть нам підпис анонімної функції (Int => Int =)
Методи : Вони не є об'єктами, а призначаються екземпляру класу, тобто об'єкту. Точно так само, як метод у java або члені функції в c ++ (як Раффі Хатчадуріан вказував у коментарі до цього питання ) і т. Д. Простий приклад методу так само, як нижче:
def m1(x: Int) = x + x
m1(2) // 4
Рядок вище - це не просте призначення значення, а визначення методу. Коли ви викликаєте цей метод зі значенням 2, як і другий рядок, х заміщається на 2, і результат буде обчислено, і ви отримаєте 4 як вихід. Тут ви отримаєте помилку, якщо просто напишіть m1, оскільки це метод і потрібно вхідне значення. За допомогою _ ви можете призначити метод такій функції, як нижче:
val f2 = m1 _ // Int => Int = <function1>
Ось чудовий пост Роб Норріс, який пояснює різницю, ось TL; DR
Методи у Scala - це не значення, а функції. Ви можете побудувати функцію, яка делегує методу за допомогою η-розширення (спровокованого тимчасовим підкресленням).
із наступним визначенням:
метод є те , що визначається з опр і значенням є те , що ви можете призначити на валу
Коротко кажучи ( витяг із блогу ):
Коли ми визначаємо метод, ми бачимо, що не можемо призначити його a val
.
scala> def add1(n: Int): Int = n + 1
add1: (n: Int)Int
scala> val f = add1
<console>:8: error: missing arguments for method add1;
follow this method with `_' if you want to treat it as a partially applied function
val f = add1
Відзначимо також тип з add1
, який не виглядає нормально; ви не можете оголосити змінну типу(n: Int)Int
. Методи не є значеннями.
Однак, додавши оператор постфікса η-розширення (η вимовляється "ета"), ми можемо перетворити метод у значення функції. Зверніть увагу на тип f
.
scala> val f = add1 _
f: Int => Int = <function1>
scala> f(3)
res0: Int = 4
Ефект _
полягає в тому, щоб виконати еквівалент наступного: ми побудуємо Function1
екземпляр, який делегує наш метод.
scala> val g = new Function1[Int, Int] { def apply(n: Int): Int = add1(n) }
g: Int => Int = <function1>
scala> g(3)
res18: Int = 4
У Scala 2.13, на відміну від функцій, методи можуть приймати / повертати
Однак ці обмеження скасовуються в крапці (Scala 3) поліморфними типами функцій № 4672 , наприклад, точка точка версії 0.23.0-RC1 забезпечує наступний синтаксис
Тип параметрів
def fmet[T](x: List[T]) = x.map(e => (e, e))
val ffun = [T] => (x: List[T]) => x.map(e => (e, e))
Неявні параметри ( контекстні параметри)
def gmet[T](implicit num: Numeric[T]): T = num.zero
val gfun: [T] => Numeric[T] ?=> T = [T] => (using num: Numeric[T]) => num.zero
Залежні типи
class A { class B }
def hmet(a: A): a.B = new a.B
val hfun: (a: A) => a.B = hmet
Для додаткових прикладів дивіться тести / run / polymorphic-function.scala
Практично програмісту Scala потрібно знати лише три правила для правильного використання функцій та методів:
def
та функціональні літерали, визначені =>
функціями. Це визначено на стор. 143, глава 8 у книзі програмування у Скалі, 4-е видання.someNumber.foreach(println)
Після чотирьох видань «Програмування в Scala» людям залишається проблемою розмежувати два важливі поняття: функцію та значення функції, оскільки всі видання не дають чіткого пояснення. Специфікація мови є надто складною. Я знайшов вищезазначені правила прості та точні.