Разница между методом и функцией в Scala
Я читаю Scala Functions (часть другого тура по Scala). В этом посте он заявил:
Методы и функции не одно и то же
Но он ничего не объяснил по этому поводу. Что он пытался сказать?
12 ответов
Джим довольно подробно рассказал об этом в своем блоге, но я публикую здесь брифинг для справки.
Для начала посмотрим, что нам скажет спецификация 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 параметров типов T1, T2,..., 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 ""
Я думаю, что это в значительной степени охватывает все, но я буду рад дополнить это ответами на любые вопросы, которые могут остаться.
Одно большое практическое различие между методом и функцией заключается в том, что 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
функция Функция может быть вызвана со списком аргументов для получения результата. Функция имеет список параметров, тело и тип результата. Функции, которые являются членами класса, свойства или одноэлементного объекта, называются методами. Функции, определенные внутри других функций, называются локальными функциями. Функции с типом результата 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
Посмотрите следующий урок, который объясняет передачу других различий с примерами, такими как другой пример diff с методом против функции, использование функции в качестве переменных, создание функции, возвращающей функцию
Функции не поддерживают параметры по умолчанию. Методы делают. Преобразование метода в функцию теряет значения параметров по умолчанию. (Scala 2.8.1)
Здесь есть хорошая статья, из которой взято большинство моих описаний. Просто краткое сравнение функций и методов моего понимания. Надеюсь, поможет:
Функции: они в основном объект. Точнее, функции - это объекты с методом 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++ (как указывал Raffi Khatchadourian в комментарии к этому вопросу) и т. Д. Простой пример метода такой же, как ниже:
def m1(x: Int) = x + x
m1(2) // 4
Строка выше - это не простое присвоение значения, а определение метода. Когда вы вызываете этот метод со значением 2, как во второй строке, x заменяется на 2, и результат будет вычислен, и вы получите 4 в качестве вывода. Здесь вы получите ошибку, если просто напишите m1, потому что это метод и нужно ввести значение. Используя _, вы можете назначить метод для функции, как показано ниже:
val f2 = m1 _ // Int => Int = <function1>
Вот отличный пост Роба Норриса, который объясняет разницу, вот TL;DR
Методы в Scala - это не значения, а функции. Вы можете создать функцию, которая делегирует методу через η-разложение (запускаемое конечным подчеркиванием).
со следующим определением:
метод - это что-то определенное с помощью def, а значение - это то, что вы можете присвоить
В двух словах (выдержка из блога):
Когда мы определяем метод, мы видим, что мы не можем присвоить его 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
На практике программисту Scala достаточно знать следующие три правила для правильного использования функций и методов:
- Методы, определенные
def
и функциональные литералы, определяемые=>
являются функциями. Он определен на странице 143 главы 8 книги "Программирование на Scala", 4-е издание. - Значения функций - это объекты, которые можно передавать как любые значения. Функциональные литералы и частично применяемые функции являются значениями функций.
- Вы можете не подчеркивать частично примененную функцию, если значение функции требуется в какой-то момент кода. Например:
someNumber.foreach(println)
После четырех выпусков "Программирования на Scala" людям все еще остается проблемой различать два важных понятия: функция и значение функции, потому что все выпуски не дают четкого объяснения. Спецификация языка слишком сложна. Я обнаружил, что приведенные выше правила просты и точны.
В Scala 2.13, в отличие от функций, методы могут принимать / возвращать
- параметры типа (полиморфные методы)
- неявные параметры
- зависимые типы
Однако эти ограничения снимаются в dotty (Scala 3) с помощью типов полиморфных функций #4672, например, версия dotty 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.
Разница тонкая, но существенная, и она связана с используемой системой типов (помимо номенклатуры, исходящей из объектно-ориентированной или функциональной парадигмы).
Когда мы говорим о функции , мы говорим о типе Function: поскольку это тип, его экземпляр может передаваться в качестве ввода или вывода в другие функции (по крайней мере, в случае Scala).
Когда мы говорим о методе (класса), мы на самом деле говорим о типе, представленном классом, частью которого он является: то есть метод является просто компонентом большего типа и не может передаваться сам по себе. Он должен передаваться вместе с экземпляром типа, частью которого он является (т. е. экземпляром класса).
Метод принадлежит объекту (обычноclass
,trait
илиobject
в котором вы ее определяете), тогда как функция сама по себе является значением, и поскольку в Scala каждое значение является объектом , следовательно, функция является объектом .
Например, учитывая метод и функцию ниже:
def timesTwoMethod(x :Int): Int = x * 2
def timesTwoFunction = (x: Int) => x * 2
Второй объект типаInt => Int
(синтаксический сахар дляFunction1[Int, Int]
).
Scala сделала функции объектами, чтобы их можно было использовать как первоклассные сущности. Таким образом, вы можете передавать функции другим функциям в качестве аргументов.
Однако Scala также может рассматривать методы как функции с помощью механизма, называемого Eta Expansion.
Например, функция высшего порядкаmap
определено наList
, получает другую функциюf: A => B
как его единственный параметр. Следующие две строки эквивалентны:
List(1, 2, 3).map(timesTwoMethod)
List(1, 2, 3).map(timesTwoFunction)
Когда компилятор видитdef
данный в том месте, где нужна функция, он автоматически преобразует метод в эквивалентную функцию.
Метод работает с объектом, а функция - нет.
Scala и C ++ имеют Fuction, но в JAVA вы должны имитировать их с помощью статических методов.