Różnica między metodą a funkcją w Scali


Odpowiedzi:


238

Jim napisał o tym prawie na swoim blogu , ale zamieszczam tutaj informacje w celu odniesienia.

Najpierw zobaczmy, co mówi nam specyfikacja Scala. Rozdział 3 (typy) mówi nam o typach funkcji (3.2.9) i typach metod (3.3.1). Rozdział 4 (podstawowe deklaracje) mówi o deklaracji wartości i definicjach (4.1), deklaracji zmiennych i definicjach (4.2) oraz deklaracjach i definicjach funkcji (4.6). Rozdział 6 (wyrażenia) mówi o funkcjach anonimowych (6.23) i wartościach metod (6.7). Co ciekawe, o wartościach funkcji mówi się jeden raz w 3.2.9 i nigdzie indziej.

Typ funkcji jest (w przybliżeniu) rodzaj postaci (T1, ..., Tn) => U , która jest skrótem dla tej cechy FunctionNw bibliotece standardowej. Anonimowe funkcje i metody Wartości mają typy funkcji, a typy funkcji mogą być używane jako część deklaracji i definicji wartości, zmiennych i funkcji. W rzeczywistości może być częścią typu metody.

Metoda Type to typ non-wartość . Oznacza to, że nie ma wartości - żadnego obiektu, żadnej instancji - z typem metody. Jak wspomniano powyżej, wartość metody faktycznie ma typ funkcji . Typ metody to defdeklaracja - wszystko defoprócz ciała.

Wartość Deklaracje i Definicje i deklaracje zmiennych i definicjevali vardeklaracje, w tym zarówno rodzajem i wartością - co może być, odpowiednio, Typ funkcji i anonimowych funkcji lub wartości Method . Zauważ, że w JVM te (wartości metod) są implementowane za pomocą tego, co Java nazywa „metodami”.

Funkcja deklaracji jest defdeklaracja, w tym rodzaj i ciała . Część typu jest typem metody, a treść jest wyrażeniem lub blokiem . Jest to również zaimplementowane w JVM za pomocą tego, co Java nazywa „metodami”.

Wreszcie, funkcja anonimowa jest instancją typu funkcji (tj. Instancją cechy FunctionN), a wartość metody jest taka sama! Różnica polega na tym, że wartość metody jest tworzona z metod, albo po poprawce podkreślenia ( m _jest to wartość metody odpowiadająca „deklaracji funkcji” ( def) m), albo w procesie zwanym eta-rozszerzeniem , który jest jak rzutowanie automatyczne z metody funkcjonować.

Tak mówią specyfikacje, więc postawię to na wstępie: nie używamy tej terminologii! Prowadzi to do zbyt dużego nieporozumienia między tak zwaną „deklaracją funkcji” , która jest częścią programu (rozdział 4 - podstawowe deklaracje), a „funkcją anonimową” , która jest wyrażeniem, a „typem funkcji” , która jest: no typ - cecha.

Poniższa terminologia, stosowana przez doświadczonych programistów Scala, wprowadza jedną zmianę w stosunku do terminologii specyfikacji: zamiast powiedzieć deklarację funkcji , mówimy metodę . Lub nawet deklaracja metody. Ponadto zauważamy, że deklaracje wartości i deklaracje zmiennych są również metodami do celów praktycznych.

Biorąc pod uwagę powyższą zmianę terminologii, oto praktyczne wyjaśnienie tego rozróżnienia.

Funkcja jest obiektem, który zawiera jedną z FunctionXcech, takich jak Function0, Function1, Function2, itd. To może być tym PartialFunction, jak dobrze, co rzeczywiście rozszerza Function1.

Zobaczmy podpis typu dla jednej z tych cech:

trait Function2[-T1, -T2, +R] extends AnyRef

Ta cecha ma jedną abstrakcyjną metodę (ma też kilka konkretnych metod):

def apply(v1: T1, v2: T2): R

I to mówi nam wszystko, co trzeba o tym wiedzieć. Funkcja ma applymetodę, która odbiera N parametry typu T1 , T2 , ..., TN , i powroty coś typu R. Jest przeciwny do otrzymywanych parametrów, a drugi do wyniku.

Ta wariancja oznacza, że ​​a Function1[Seq[T], String]jest podtypem Function1[List[T], AnyRef]. Bycie podtypem oznacza, że ​​można go użyć zamiast niego. Łatwo zauważyć, że jeśli mam zadzwonić f(List(1, 2, 3))i spodziewać się AnyRefoddzwaniania, oba powyższe typy działałyby.

Jakie jest podobieństwo metody i funkcji? Cóż, jeśli fjest funkcją i mjest metodą lokalną dla zakresu, to oba można wywołać w następujący sposób:

val o1 = f(List(1, 2, 3))
val o2 = m(List(1, 2, 3))

Te połączenia są w rzeczywistości różne, ponieważ pierwszy to tylko cukier syntaktyczny. Scala rozwija go do:

val o1 = f.apply(List(1, 2, 3))

Co oczywiście jest wywołaniem metody na obiekcie f. Funkcje mają także inne cukry składniowe na swoją korzyść: literały funkcyjne (właściwie dwa) i (T1, T2) => Rpodpisy tekstowe. Na przykład:

val f = (l: List[Int]) => l mkString ""
val g: (AnyVal) => String = {
  case i: Int => "Int"
  case d: Double => "Double"
  case o => "Other"
}

Innym podobieństwem między metodą a funkcją jest to, że pierwszą można łatwo przekonwertować na drugą:

val f = m _

Scala rozszerzy to , zakładając, że mtypem jest (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)
}

W Scali 2.8 używa AbstractFunction1klasy, aby zmniejszyć rozmiary klas.

Zauważ, że nie da się przekonwertować na odwrót - z funkcji na metodę.

Metody mają jednak jedną dużą zaletę (cóż, dwie - mogą być nieco szybsze): mogą odbierać parametry typu . Na przykład, chociaż fpowyżej można koniecznie określić typ Listotrzymywanego ( List[Int]w przykładzie), mmożna go sparametryzować:

def m[T](l: List[T]): String = l mkString ""

Myślę, że w zasadzie obejmuje to wszystko, ale chętnie uzupełnię to odpowiedziami na wszelkie pytania, które mogą pozostać.


26
To wyjaśnienie jest bardzo jasne. Dobra robota. Niestety zarówno książka Odersky / Venners / Spoon, jak i specyfikacja Scali używają słów „funkcja” i „metoda” zamiennie. (Najłatwiej jest powiedzieć „funkcja”, gdzie „metoda” byłaby bardziej zrozumiała, ale czasami dzieje się to również w drugą stronę, na przykład sekcja 6.7 specyfikacji, która dotyczy konwertowania metod na funkcje, nosi nazwę „Wartości metod”. Ugh .) Myślę, że luźne użycie tych słów doprowadziło do wielu nieporozumień, kiedy ludzie próbują nauczyć się języka.
Seth Tisue

4
@Seth Wiem, wiem - PinS była książką, która nauczyła mnie Scali. Nauczyłem się lepiej na własnej skórze, tj. Paulp wyprostował mnie.
Daniel C. Sobral

4
Świetne wyjaśnienie! Muszę dodać jedną rzecz: kiedy cytujesz rozszerzenie val f = mkompilatora val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }, powinieneś zauważyć, że metoda thiswewnątrz applynie odnosi się do AnyRefobiektu, ale do obiektu, w którego metodzie val f = m _jest oceniana ( zewnętrzna this , że tak powiem ), ponieważ thisnależy do wartości uchwyconych przez zamknięcie (jak np. returnjak wskazano poniżej).
Holger Peine

1
@ DanielC.Sobral, o jakiej książce PinS wspomniałeś? Interesuję się również nauką Scali i nie znalazłem książki o takiej nazwie
tldr

5
@tldr Programming in Scala , autor: Odersky i inni. Jest to jego powszechny skrót (powiedzieli mi, że z jakiegoś powodu nie lubili PiS!)
Daniel C. Sobral

67

Jedną dużą praktyczną różnicą między metodą a funkcją jest to, co returnoznacza. returntylko zawsze wraca z metody. Na przykład:

scala> val f = () => { return "test" }
<console>:4: error: return outside method definition
       val f = () => { return "test" }
                       ^

Zwracanie z funkcji zdefiniowanej w metodzie powoduje nielokalny zwrot:

scala> def f: String = {                 
     |    val g = () => { return "test" }
     | g()                               
     | "not this"
     | }
f: String

scala> f
res4: String = test

Natomiast powrót z metody lokalnej zwraca tylko z tej metody.

scala> def f2: String = {         
     | def g(): String = { return "test" }
     | g()
     | "is this"
     | }
f2: String

scala> f2
res5: String = is this

9
To dlatego, że zwrot jest ujmowany przez zamknięcie.
Daniel C. Sobral

4
Nie mogę myśleć o żadnym momencie, w którym chciałbym „powrócić” z funkcji do zakresu nielokalnego. W rzeczywistości widzę to jako poważny problem bezpieczeństwa, jeśli funkcja może po prostu zdecydować, że chce pójść dalej w górę stosu. Czuje się jak longjmp, tylko o wiele łatwiej jest przypadkowo się pomylić. Zauważyłem jednak, że scalac nie pozwala mi wrócić z funkcji. Czy to oznacza, że ​​ta obrzydliwość została wykreślona z języka?
root

2
@root - co powiesz na powrót ze środka for (a <- List(1, 2, 3)) { return ... }? To zostaje pozbawione cukru do zamknięcia.
Ben Lings,

Hmm ... To rozsądny przypadek użycia. Wciąż może prowadzić do okropnych problemów trudnych do debugowania, ale stawia to w bardziej sensownym kontekście.
root

1
Szczerze mówiąc, użyłbym innej składni. mają returnzwrócić wartość od funkcji, a część formy escapei breaklub continuedo powrotu z metod.
Ryan The Leach

38

funkcja Funkcję można wywołać z listą argumentów w celu uzyskania wyniku. Funkcja ma listę parametrów, treść i typ wyniku. Funkcje należące do obiektu klasy, cechy lub obiektu singleton są nazywane metodami . Funkcje zdefiniowane w innych funkcjach nazywane są funkcjami lokalnymi. Funkcje z wynikiem typu Jednostka nazywane są procedurami. Anonimowe funkcje w kodzie źródłowym nazywane są literałami funkcji. W czasie wykonywania literały funkcji są tworzone w obiektach zwanych wartościami funkcji.

Programowanie w Scala Wydanie drugie. Martin Odersky - Lex Spoon - Bill Venners


1
Funkcja może należeć do klasy jako def lub val / var. Tylko def są metodami.
Josiah Yoder,

29

Pozwól, że masz listę

scala> val x =List.range(10,20)
x: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)

Zdefiniuj metodę

scala> def m1(i:Int)=i+2
m1: (i: Int)Int

Zdefiniuj funkcję

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)

Metoda akceptowania argumentu

scala> m1(2)
res3: Int = 4

Definiowanie funkcji za pomocą val

scala> val p =(i:Int)=>i+2
p: Int => Int = <function1>

Argument funkcji jest opcjonalny

 scala> p(2)
    res4: Int = 4

scala> p
res5: Int => Int = <function1>

Argument do metody jest obowiązkowy

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

Sprawdź następujący samouczek, który wyjaśnia przekazywanie innych różnic przykładami, takimi jak inny przykład różnicy z funkcją Metoda Vs, Używanie funkcji jako zmiennych, tworzenie funkcji, która zwróciła funkcję


13

Funkcje nie obsługują wartości domyślnych parametrów. Metody działają. Przekształcenie z metody na funkcję traci wartości domyślne parametrów. (Scala 2.8.1)


5
Czy jest ku temu powód?
corazza

7

Jest tutaj fajny artykuł , z którego pochodzi większość moich opisów. Krótkie porównanie funkcji i metod dotyczących mojego zrozumienia. Mam nadzieję, że to pomoże:

Funkcje : Są w zasadzie przedmiotem. Dokładniej, funkcje są obiektami z zastosowaniem metody; Dlatego są nieco wolniejsze niż metody ze względu na narzut. Jest podobny do metod statycznych w tym sensie, że są one niezależne od obiektu, który ma zostać wywołany. Prosty przykład funkcji jest jak poniżej:

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

Linia powyżej to nic innego jak przypisanie jednego obiektu do drugiego, np. Object1 = object2. W rzeczywistości obiekt2 w naszym przykładzie jest funkcją anonimową, dlatego lewa strona otrzymuje z tego powodu typ obiektu. Dlatego teraz f1 jest obiektem (funkcją). Funkcja anonimowa jest w rzeczywistości instancją funkcji 1 [Int, Int], co oznacza funkcję z 1 parametrem typu Int i zwracaną wartością typu Int. Wywołanie f1 bez argumentów da nam podpis funkcji anonimowej (Int => Int =)

Metody : Nie są obiektami, ale są przypisane do instancji klasy, tj. Obiektu. Dokładnie taka sama jak metoda w java lub funkcje składowe w c ++ (jak zauważył Raffi Khatchadourian w komentarzu do tego pytania ) i itp. Prosty przykład metody jest podobny do poniższego :

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

Linia powyżej nie jest prostym przypisaniem wartości, ale definicją metody. Kiedy wywołujesz tę metodę z wartością 2, jak w drugim wierszu, x jest zastępowane przez 2, a wynik zostanie obliczony i otrzymasz 4 jako wynik. Tutaj pojawi się błąd, jeśli po prostu napisz m1, ponieważ jest to metoda i potrzebujesz wartości wejściowej. Za pomocą _ możesz przypisać metodę do funkcji takiej jak poniżej:

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

Co to znaczy „przypisać metodę do funkcji”? Czy to tylko oznacza, że ​​masz obiekt, który zachowuje się tak samo jak metoda?
K. M,

@ KM: val f2 = m1 _ jest równoważne val f2 = nowa funkcja 1 [Int, Int] {def m1 (x: Int) = x + x};
sasuke

3

Oto świetny post Roba Norrisa, który wyjaśnia różnicę, oto TL; DR

Metody w Scali nie są wartościami, ale funkcjami są. Możesz skonstruować funkcję, która deleguje metodę do rozszerzenia η (wywoływanego przez końcowy znak podkreślenia).

z następującą definicją:

metoda jest coś zdefiniowane z def a wartość jest czymś, co można przypisać do val

W skrócie ( wyciąg z bloga ):

Kiedy definiujemy metodę, widzimy, że nie możemy jej przypisać do 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

Należy również zwrócić uwagę na typ z add1, co nie wygląda normalnie; nie można zadeklarować zmiennej typu (n: Int)Int. Metody nie są wartościami.

Jednak dodając operator postfiksu rozszerzenia η (η jest wymawiane jako „eta”), możemy przekształcić metodę w wartość funkcji. Zwróć uwagę na rodzaj f.

scala> val f = add1 _
f: Int => Int = <function1>

scala> f(3)
res0: Int = 4

Efektem _jest wykonanie równoważnika następujących czynności: konstruujemy Function1instancję, która deleguje się do naszej metody.

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

W Scali 2.13, w przeciwieństwie do funkcji, metody mogą przyjmować / zwracać

  • parametry typu (metody polimorficzne)
  • parametry niejawne
  • typy zależne

Jednak ograniczenia te są zniesione w dotty (Scala 3) według typów funkcji polimorficznych # 4672 , na przykład wersja dotty 0.23.0-RC1 umożliwia następującą składnię

Wpisz parametry

def fmet[T](x: List[T]) = x.map(e => (e, e))
val ffun = [T] => (x: List[T]) => x.map(e => (e, e))

Parametry niejawne ( parametry kontekstu )

def gmet[T](implicit num: Numeric[T]): T = num.zero
val gfun: [T] => Numeric[T] ?=> T = [T] => (using num: Numeric[T]) => num.zero

Zależne typy

class A { class B }
def hmet(a: A): a.B = new a.B
val hfun: (a: A) => a.B = hmet

Aby uzyskać więcej przykładów, zobacz testy / run / polymorphic-functions.scala


0

Praktycznie programista Scala musi znać tylko następujące trzy zasady, aby prawidłowo używać funkcji i metod:

  • Metody zdefiniowane przez defi literały funkcji zdefiniowane przez =>są funkcjami. Jest to zdefiniowane na stronie 143, rozdział 8, w książce Programowanie w Scali, wydanie 4.
  • Wartości funkcji to obiekty, które można przekazywać jako dowolne wartości. Literały funkcji i częściowo zastosowane funkcje są wartościami funkcji.
  • Możesz pominąć podkreślenie częściowo zastosowanej funkcji, jeśli w punkcie kodu wymagana jest wartość funkcji. Na przykład:someNumber.foreach(println)

Po czterech edycjach Programowania w Scali nadal jest problem, aby ludzie rozróżnili dwie ważne koncepcje: funkcję i wartość funkcji, ponieważ wszystkie edycje nie dają jasnego wyjaśnienia. Specyfikacja języka jest zbyt skomplikowana. Odkryłem, że powyższe zasady są proste i dokładne.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.