Przeczytałem Scala Functions (część Another tour of Scala ). W tym poście stwierdził:
Metody i funkcje to nie to samo
Ale nic nie wyjaśnił. Co on chciał powiedzieć?
Przeczytałem Scala Functions (część Another tour of Scala ). W tym poście stwierdził:
Metody i funkcje to nie to samo
Ale nic nie wyjaśnił. Co on chciał powiedzieć?
Odpowiedzi:
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 FunctionN
w 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 def
deklaracja - wszystko def
oprócz ciała.
Wartość Deklaracje i Definicje i deklaracje zmiennych i definicje są val
i var
deklaracje, 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 def
deklaracja, 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 FunctionX
cech, 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 apply
metodę, 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ę AnyRef
oddzwaniania, oba powyższe typy działałyby.
Jakie jest podobieństwo metody i funkcji? Cóż, jeśli f
jest funkcją i m
jest 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) => R
podpisy 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 m
typem 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 AbstractFunction1
klasy, 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ż f
powyżej można koniecznie określić typ List
otrzymywanego ( List[Int]
w przykładzie), m
moż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ć.
val f = m
kompilatora val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }
, powinieneś zauważyć, że metoda this
wewnątrz apply
nie odnosi się do AnyRef
obiektu, ale do obiektu, w którego metodzie val f = m _
jest oceniana ( zewnętrzna this
, że tak powiem ), ponieważ this
należy do wartości uchwyconych przez zamknięcie (jak np. return
jak wskazano poniżej).
Jedną dużą praktyczną różnicą między metodą a funkcją jest to, co return
oznacza. return
tylko 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
for (a <- List(1, 2, 3)) { return ... }
? To zostaje pozbawione cukru do zamknięcia.
return
zwrócić wartość od funkcji, a część formy escape
i break
lub continue
do powrotu z metod.
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
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ę
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>
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 Function1
instancję, 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
W Scali 2.13, w przeciwieństwie do funkcji, metody mogą przyjmować / zwracać
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
Praktycznie programista Scala musi znać tylko następujące trzy zasady, aby prawidłowo używać funkcji i metod:
def
i literały funkcji zdefiniowane przez =>
są funkcjami. Jest to zdefiniowane na stronie 143, rozdział 8, w książce Programowanie w Scali, wydanie 4.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.