Разница между методом и функцией в Scala


254

Я читаю Scala Functions (часть другого тура по Scala ). В этом посте он заявил:

Методы и функции не одно и то же

Но он ничего не объяснил по этому поводу. Что он пытался сказать?



3
Я думаю, что вы можете получить что-то от разницы между методом и функцией
jinglining

Дополнительный вопрос с хорошими ответами: функции против методов в Scala
Джозия Йодер

Ответы:


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объявление, включая тип и тело . Часть type является типом метода, а тело является выражением или блоком . Это также реализовано в JVM с помощью того, что Java называет «методами».

Наконец, анонимная функция является экземпляром типа функции (т. Е. Экземпляром признака FunctionN), а значение метода - это то же самое! Различие состоит в том, что значение метода создается из методов, либо путем добавления нижнего подчеркивания ( m _это значение метода, соответствующее «объявлению функции» ( def) m), либо с помощью процесса, называемого eta-extension , который похож на автоматическое приведение из метода. функционировать.

Это то, что говорят спецификации, поэтому позвольте мне сказать об этом заранее: мы не используем эту терминологию! Это приводит к слишком большой путанице между так называемым «объявлением функции» , которое является частью программы (глава 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
Это объяснение очень ясно. Отлично сработано. К сожалению, в книге Одерского / Веннерса / Ложки и в спецификации Scala слова «функция» и «метод» взаимозаменяемы. (Скорее всего, они говорят «функция», где «метод» будет более понятным, но иногда это происходит и другим способом, например, раздел 6.7 спецификации, который охватывает преобразование методов в функции, называется «Значения метода». Тьфу .) Я думаю, что свободное использование этих слов привело к путанице, когда люди пытаются выучить язык.
Сет Тиуэ

4
@ Знаете, я знаю - PinS была книга, которая научила меня Скала. Я лучше усвоил трудный путь, т. Е. Пауль поправил меня.
Даниэль С. Собрал

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 не позволит мне вернуться из функций. Означает ли это, что эта мерзость вычеркнута из языка?
root

2
@root - как насчет возвращения изнутри for (a <- List(1, 2, 3)) { return ... }? Это обезвожено до закрытия.
Бен Лингс

Хм ... Ну, это разумный вариант использования. По-прежнему имеет потенциал, который может привести к ужасным трудным для отладки проблемам, но это ставит его в более разумный контекст.
root

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

Проверьте следующий учебник, который объясняет передачу других различий с примерами, такими как другой пример diff с методом против функции, использование функции в качестве переменных, создание функции, возвращающей функцию


13

Функции не поддерживают параметры по умолчанию. Методы делают. Преобразование из метода в функцию теряет значения параметров по умолчанию. (Scala 2.8.1)


5
Есть ли причина для этого?
Корацца

7

Существует хорошая статья здесь , из которых большинство моих описаний принимаются. Просто краткое сравнение функций и методов относительно моего понимания. Надеюсь, поможет:

Функции : они в основном объект. Точнее, функции - это объекты с методом apply; Таким образом, они немного медленнее, чем методы из-за их накладных расходов. Это похоже на статические методы в том смысле, что они не зависят от вызываемого объекта. Простой пример функции выглядит так:

val f1 = (x: Int) => x + x
f1(2)  // 4

Строка выше - это ничто иное, как присвоение одного объекта другому, например, object1 = object2. На самом деле object2 в нашем примере является анонимной функцией, и левая сторона получает тип объекта из-за этого. Следовательно, теперь f1 является объектом (функцией). На самом деле анонимная функция является экземпляром Function1 [Int, Int], что означает функцию с 1 параметром типа Int и возвращаемым значением типа Int. Вызов f1 без аргументов даст нам подпись анонимной функции (Int => Int =)

Методы : они не являются объектами, но присваиваются экземпляру класса, то есть объекту. Точно так же, как метод в java или функции-члены в c ++ (как отметил Раффи Хачатурян в комментарии к этому вопросу ) и т. Д. Простой пример метода такой же, как и ниже:

def m1(x: Int) = x + x
m1(2)  // 4

Строка выше - это не простое присвоение значения, а определение метода. Когда вы вызываете этот метод со значением 2, как во второй строке, x заменяется на 2, и результат будет вычислен, и вы получите 4 в качестве вывода. Здесь вы получите ошибку, если просто напишите m1, потому что это метод и нужно ввести значение. Используя _, вы можете назначить метод для функции, как показано ниже:

val f2 = m1 _  // Int => Int = <function1>

Что значит «назначить метод функции»? Означает ли это, что теперь у вас есть объект, который ведет себя так же, как и метод?
К. М.

@KM: val f2 = m1 _ эквивалентен val f2 = new Function1 [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. Методы не являются значениями.

Однако, добавив постфиксный оператор η-расширения (η произносится как «eta»), мы можем превратить метод в значение функции. Обратите внимание на тип 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

Дополнительные примеры см. В разделе tests / run / polymorphic-functions.scala.


0

Практически, программист Scala должен знать только следующие три правила, чтобы правильно использовать функции и методы:

  • Методы, определенные defи функциональные литералы, определенные =>как функции, являются функциями. Это определено на стр. 143, глава 8 книги по программированию в Scala, 4-е издание.
  • Значения функций - это объекты, которые можно передавать как любые значения. Функциональные литералы и частично примененные функции являются значениями функций.
  • Вы можете пропустить подчеркивание частично примененной функции, если в точке кода требуется значение функции. Например:someNumber.foreach(println)

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.