Різниця між методом і функцією в Scala


254

Я читав функції Scala (частина чергового туру Скали ). У цій посаді він заявив:

Методи та функції - це не одне і те ж

Але він нічого про це не пояснив. Що він намагався сказати?



3
Я думаю, ви можете отримати щось із того, в чому різниця між методом і функцією
jinglining

Наступне запитання з хорошими відповідями: Функції та методи у Scala
Josiah Yoder

Відповіді:


238

Джим розкрив це в своєму дописі на блозі , але я розміщую тут брифінг для довідки.

Спочатку давайте подивимось, що нам розповідає Специфікація 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 ""

Я думаю, що це в значній мірі охоплює все, але я радий доповнити це відповідями на будь-які питання, які можуть залишитися.


26
Це пояснення дуже зрозуміле. Молодці. На жаль, і Одерський / Веннери / Ложка, і специфікація Скала використовують слова "функція" та "метод" дещо взаємозамінно. (Найімовірніше, вони говорять "функція", де "метод" був би зрозумілішим, але іноді це відбувається і іншим способом, наприклад, розділ 6.7 специфікації, який охоплює перетворення методів у функції, називається "Значення методу". .) Я думаю, що слабке використання цих слів призвело до великої плутанини, коли люди намагаються вивчити мову.
Seth Tisue

4
@Seth Я знаю, я знаю - PinS була книгою, яка навчила мене Scala. Я навчився краще нелегкий шлях, тобто паулп поставив мене прямо.
Даніель К. Собрал

4
Чудове пояснення! У мене є ще одне, що слід додати: Коли ви цитуєте розширення 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як зазначено нижче).
Хольгер Пейн

1
@ DanielC.Sobral, що таке книга PinS, яку ви згадали? Я також зацікавлений у вивченні Scala, і не знайшов книги з таким ім'ям,
tldr

5
@tldr Програмування в Scala , Одерський та ін. Це загальна абревіатура для цього (вони мені сказали, що вони чомусь не сподобалися PiS! :)
Даніель К. Собрал,

67

Одна велика практична відмінність методу від функції - це те, що 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

9
Це тому, що повернення охоплює закриття.
Даніель К. Собрал

4
Я не можу придумати жодного разу, коли б хотів «повернутися» з функції в нелокальну область. Насправді я бачу, що це є серйозною проблемою безпеки, якщо функція може просто вирішити, що вона хоче піти далі назад в стек. Відчуває себе подібним до longjmp, тільки легше випадково помилитися. Я помітив, що scalac не дозволить мені повернутися з функцій. Чи означає це, що ця гидота була викреслена з мови?
корінь

2
@root - що з поверненням зсередини for (a <- List(1, 2, 3)) { return ... }? Це призупиняється до закриття.
Бен Лінгс

Хм ... Ну, це розумний випадок використання. Все ще є потенціал, який може призвести до жахливих важких проблем налагодження, але це ставить це в більш розумному контексті.
корінь

1
Чесно кажучи, я використовував би різні синтаксиси. є returnповернення значення з функції, а також деякі форм escapeабо breakабо continueповертати з методів.
Райан Ліч

38

функція Функцію можна викликати зі списком аргументів для отримання результату. Функція містить список параметрів, тіло та тип результату. Функції, що входять до об'єкта класу, ознаки або однотонного, називаються методами . Функції, визначені всередині інших функцій, називаються локальними функціями. Функції з результатом типу Unit називаються процедурами. Анонімні функції у вихідному коді називаються функціональними літералами. Під час виконання функціональні літерали інстанціюються в об'єкти, які називаються функціональними значеннями.

Програмування у Scala Second Edition. Мартін Одерський - Лекс Ложка - Білл Веннерс


1
Функція може належати класу як def або як val / var. Тільки def - це методи.
Йосія Йодер

29

Скажімо, у вас є список

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 Функція, Використання функції як змінних, створення функції, яка повертає функцію


13

Функції не підтримують параметри за замовчуванням. Методи. Перетворення з методу у функцію втрачає параметри за замовчуванням. (Scala 2.8.1)


5
Чи є для цього причини?
corazza

7

Існує гарна стаття тут , з яких більшість моїх описів приймаються. Лише коротке порівняння функцій та методів щодо мого розуміння. Сподіваюся, це допомагає:

Функції : Вони в основному є об'єктом. Точніше, функції - це об'єкти із застосуванням методу; Тому вони трохи повільніше, ніж методи через їхні накладні витрати. Це аналогічно статичним методам в тому сенсі, що вони не залежать від об'єкта, на який слід викликати. Простий приклад функції так само, як нижче:

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>

Що означає "призначити метод функції"? Чи це просто означає, що у вас зараз є об’єкт, який поводиться так само, як і метод?
К. М

@KM: val f2 = m1 _ еквівалентний val f2 = нова функція1 [Int, Int] {def m1 (x: Int) = x + x};
Саске

3

Ось чудовий пост Роб Норріс, який пояснює різницю, ось 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

1

У 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


0

Практично програмісту Scala потрібно знати лише три правила для правильного використання функцій та методів:

  • Методи, визначені defта функціональні літерали, визначені =>функціями. Це визначено на стор. 143, глава 8 у книзі програмування у Скалі, 4-е видання.
  • Значення функції - це об'єкти, які можна передавати навколо будь-яких значень. Функціональні літерали та частково застосовані функції - це значення функцій.
  • Ви можете виділити підкреслення частково застосованої функції, якщо значення функції потрібне в точці коду. Наприклад:someNumber.foreach(println)

Після чотирьох видань «Програмування в Scala» людям залишається проблемою розмежувати два важливі поняття: функцію та значення функції, оскільки всі видання не дають чіткого пояснення. Специфікація мови є надто складною. Я знайшов вищезазначені правила прості та точні.

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