Unterschied zwischen Methode und Funktion in Scala


254

Ich habe Scala-Funktionen gelesen (Teil einer weiteren Tour durch Scala ). In diesem Beitrag erklärte er:

Methoden und Funktionen sind nicht dasselbe

Aber er hat nichts darüber erklärt. Was wollte er sagen?




Eine Folgefrage mit guten Antworten: Funktionen gegen Methoden in Scala
Josiah Yoder

Antworten:


238

Jim hat dies in seinem Blog-Beitrag ziemlich ausführlich behandelt , aber ich poste hier ein Briefing als Referenz.

Lassen Sie uns zunächst sehen, was uns die Scala-Spezifikation sagt. Kapitel 3 (Typen) enthält Informationen zu Funktionstypen (3.2.9) und Methodentypen (3.3.1). Kapitel 4 (Grunddeklarationen) spricht von Werterklärungen und Definitionen (4.1), Variablendeklarationen und Definitionen (4.2) und Funktionsdeklarationen und Definitionen (4.6). Kapitel 6 (Ausdrücke) spricht von anonymen Funktionen (6.23) und Methodenwerten (6.7). Seltsamerweise wird am 3.2.9 einmal von Funktionswerten gesprochen, und nirgendwo anders.

Ein Funktionstyp ist (ungefähr) ein Typ der Form (T1, ..., Tn) => U , die eine Abkürzung für das Merkmal FunctionNin der Standardbibliothek ist. Anonyme Funktionen und Methodenwerte haben Funktionstypen, und Funktionstypen können als Teil von Wert-, Variablen- und Funktionsdeklarationen und -definitionen verwendet werden. Tatsächlich kann es Teil eines Methodentyps sein.

Ein Methodentyp ist ein Nichtwerttyp . Das heißt, es gibt keinen Wert - kein Objekt, keine Instanz - mit einem Methodentyp. Wie oben erwähnt, hat ein Methodenwert tatsächlich einen Funktionstyp . Ein Methodentyp ist eine defDeklaration - alles über a defaußer seinem Körper.

Wertdeklarationen und -definitionen sowie Variablendeklarationen und -definitionen sind valund varDeklarationen, einschließlich Typ und Wert , die jeweils Funktionstyp- und anonyme Funktionen oder Methodenwerte sein können . Beachten Sie, dass diese (Methodenwerte) in der JVM mit den von Java als "Methoden" bezeichneten Methoden implementiert werden.

Eine Funktionsdeklaration ist eine defDeklaration, einschließlich Typ und Text . Der Typteil ist der Methodentyp, und der Körper ist ein Ausdruck oder ein Block . Dies wird auch in der JVM mit den von Java als "Methoden" bezeichneten Methoden implementiert.

Schließlich ist eine anonyme Funktion eine Instanz eines Funktionstyps (dh eine Instanz des Merkmals FunctionN), und ein Methodenwert ist dasselbe! Der Unterschied besteht darin, dass ein Methodenwert aus Methoden erstellt wird, entweder durch Nachfixieren eines Unterstrichs ( m _ist ein Methodenwert, der der "Funktionsdeklaration" ( def) entspricht m) oder durch einen Prozess namens eta-extension , der einer automatischen Umwandlung von method ähnelt Funktionieren.

Das sagen die Spezifikationen, also lassen Sie mich dies vorwegnehmen: Wir verwenden diese Terminologie nicht! Dies führt zu einer zu großen Verwechslung zwischen der sogenannten "Funktionsdeklaration" , die Teil des Programms ist (Kapitel 4 - Grunddeklarationen), und der "anonymen Funktion" , die ein Ausdruck ist, und dem "Funktionstyp" , dh Nun, ein Typ - eine Eigenschaft.

Die folgende Terminologie, die von erfahrenen Scala-Programmierern verwendet wird, ändert sich gegenüber der Terminologie der Spezifikation: Anstatt Funktionsdeklaration zu sagen, sagen wir Methode . Oder sogar Methodendeklaration. Darüber hinaus stellen wir fest, dass Wertdeklarationen und Variablendeklarationen auch Methoden für praktische Zwecke sind.

Angesichts der obigen Änderung der Terminologie finden Sie hier eine praktische Erklärung der Unterscheidung.

Eine Funktion ist ein Objekt , das eines der umfasst FunctionXMerkmale, wie zum Beispiel Function0, Function1, Function2usw. Es könnte sein , einschließlich PartialFunctionals auch, was sich eigentlich Function1.

Sehen wir uns die Typensignatur für eines dieser Merkmale an:

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

Dieses Merkmal hat eine abstrakte Methode (es gibt auch einige konkrete Methoden):

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

Und das sagt uns alles, was es darüber zu wissen gibt. Eine Funktion hat eine applyMethode, die N Parameter vom Typ T1 , T2 , ..., TN empfängt und etwas vom Typ zurückgibt R. Es ist eine Gegenvariante der empfangenen Parameter und eine Co-Variante des Ergebnisses.

Diese Varianz bedeutet, dass a Function1[Seq[T], String]ein Subtyp von ist Function1[List[T], AnyRef]. Ein Subtyp zu sein bedeutet, dass er anstelle davon verwendet werden kann. Man kann leicht erkennen, f(List(1, 2, 3))dass einer AnyRefder beiden oben genannten Typen funktionieren würde , wenn ich anrufen und einen Rücken erwarten würde.

Was ist nun die Ähnlichkeit einer Methode und einer Funktion? Nun, wenn fes sich um eine Funktion und meine lokale Methode im Bereich handelt, können beide folgendermaßen aufgerufen werden:

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

Diese Aufrufe sind tatsächlich unterschiedlich, da der erste nur ein syntaktischer Zucker ist. Scala erweitert es auf:

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

Was natürlich ein Methodenaufruf für ein Objekt ist f. Funktionen haben auch andere syntaktische Zucker im Vorteil: Funktionsliterale (zwei davon tatsächlich) und (T1, T2) => RTypensignaturen. Beispielsweise:

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

Eine weitere Ähnlichkeit zwischen einer Methode und einer Funktion besteht darin, dass die erstere leicht in die letztere umgewandelt werden kann:

val f = m _

Scala wird erweitert , dass unter der Annahme , mTyp ist (List[Int])AnyRefin (Scala 2.7):

val f = new AnyRef with Function1[List[Int], AnyRef] {
  def apply(x$1: List[Int]) = this.m(x$1)
}

In Scala 2.8 wird tatsächlich eine AbstractFunction1Klasse verwendet, um die Klassengröße zu reduzieren.

Beachten Sie, dass man nicht umgekehrt konvertieren kann - von einer Funktion zu einer Methode.

Methoden haben jedoch einen großen Vorteil (zwei - sie können etwas schneller sein): Sie können Typparameter empfangen . Während foben beispielsweise notwendigerweise der Typ des ListEmpfangs angegeben werden kann ( List[Int]im Beispiel), mkann es beispielsweise parametrisiert werden:

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

Ich denke, das deckt so ziemlich alles ab, aber ich werde dies gerne mit Antworten auf alle verbleibenden Fragen ergänzen.


26
Diese Erklärung ist sehr klar. Gut gemacht. Leider verwenden sowohl das Odersky / Venners / Spoon-Buch als auch die Scala-Spezifikation die Wörter "Funktion" und "Methode" etwas austauschbar. (Es ist am wahrscheinlichsten, dass sie "Funktion" sagen, wobei "Methode" klarer wäre, aber manchmal geschieht dies auch in umgekehrter Richtung. Beispielsweise wird Abschnitt 6.7 der Spezifikation, in dem die Konvertierung von Methoden in Funktionen behandelt wird, als "Methodenwerte" bezeichnet. Ugh .) Ich denke, dass der lose Gebrauch dieser Wörter zu viel Verwirrung geführt hat, wenn Leute versuchen, die Sprache zu lernen.
Seth Tisue

4
@ Seth Ich weiß, ich weiß - PinS war das Buch, das mir Scala beigebracht hat. Ich habe auf die harte Tour besser gelernt, dh Paulp hat mich gerade gestellt.
Daniel C. Sobral

4
Tolle Erklärung! Ich muss eines hinzufügen: Wenn Sie die Erweiterung von val f = mdurch den Compiler zitieren val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }, sollten Sie darauf hinweisen, dass sich das thisInnere der applyMethode nicht auf das AnyRefObjekt bezieht , sondern auf das Objekt, in dessen Methode das val f = m _ausgewertet wird ( sozusagen das Äußere) this ), da thiszu den Werten gehört, die vom Abschluss erfasst werden (wie z. B. returnwie unten angegeben).
Holger Peine

1
@ DanielC.Sobral, was ist das PinS-Buch, das du erwähnt hast? Ich bin auch daran interessiert, Scala zu lernen, und habe kein Buch mit diesem Namen gefunden
tldr

5
@tldr Programmierung in Scala , von Odersky et al . Es ist die gebräuchliche Abkürzung dafür (sie sagten mir, dass sie PiS aus irgendeinem Grund nicht wirklich mochten! :)
Daniel C. Sobral

67

Ein großer praktischer Unterschied zwischen einer Methode und einer Funktion ist, was returnbedeutet. returnkehrt immer nur von einer Methode zurück. Beispielsweise:

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

Die Rückgabe von einer in einer Methode definierten Funktion führt zu einer nicht lokalen Rückgabe:

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

scala> f
res4: String = test

Während die Rückkehr von einer lokalen Methode nur von dieser Methode zurückkehrt.

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

scala> f2
res5: String = is this

9
Das liegt daran, dass die Rückkehr durch die Schließung erfasst wird.
Daniel C. Sobral

4
Ich kann mir kein einziges Mal vorstellen, wenn ich von einer Funktion in einen nichtlokalen Bereich zurückkehren möchte. Tatsächlich kann ich sehen, dass dies ein ernstes Sicherheitsproblem ist, wenn eine Funktion nur entscheiden kann, dass sie den Stapel weiter nach oben verschieben möchte. Fühlt sich an wie longjmp, nur viel einfacher, versehentlich etwas falsch zu machen. Ich habe festgestellt, dass Scalac mich nicht von Funktionen zurückkehren lässt. Bedeutet das, dass dieser Gräuel aus der Sprache gestrichen wurde?
Wurzel

2
@root - was ist mit der Rückkehr aus einem for (a <- List(1, 2, 3)) { return ... }? Das wird zu einer Schließung entzuckert.
Ben Lings

Hmm ... Nun, das ist ein vernünftiger Anwendungsfall. Hat immer noch das Potenzial, zu schrecklichen, schwer zu debuggenden Problemen zu führen, aber das bringt es in einen vernünftigeren Kontext.
Wurzel

1
Ehrlich gesagt würde ich eine andere Syntax verwenden. haben returneinen Wert von der Funktion und eine Form von escapeoder breakoder continuevon Methoden zurückgegeben.
Ryan The Leach

38

Funktion Eine Funktion kann mit einer Liste von Argumenten aufgerufen werden, um ein Ergebnis zu erzielen. Eine Funktion verfügt über eine Parameterliste, einen Body und einen Ergebnistyp. Funktionen, die Mitglieder einer Klasse, eines Merkmals oder eines Singleton-Objekts sind, werden als Methoden bezeichnet . In anderen Funktionen definierte Funktionen werden als lokale Funktionen bezeichnet. Funktionen mit dem Ergebnistyp der Einheit werden Prozeduren genannt. Anonyme Funktionen im Quellcode werden als Funktionsliterale bezeichnet. Zur Laufzeit werden Funktionsliterale in Objekte instanziiert, die als Funktionswerte bezeichnet werden.

Programmierung in Scala Second Edition. Martin Odersky - Lex Löffel - Bill Venners


1
Eine Funktion kann als def oder als val / var zu einer Klasse gehören. Nur die Defs sind Methoden.
Josiah Yoder

29

Angenommen, Sie haben eine Liste

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

Definieren Sie eine Methode

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

Definieren Sie eine Funktion

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)

Methode, die das Argument akzeptiert

scala> m1(2)
res3: Int = 4

Funktion mit val definieren

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

Das Argument für die Funktion ist optional

 scala> p(2)
    res4: Int = 4

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

Das Argument zur Methode ist obligatorisch

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

Überprüfen Sie das folgende Lernprogramm , in dem erläutert wird, wie andere Unterschiede anhand von Beispielen wie anderen Beispielen für diff mit Methode Vs-Funktion übergeben werden. Verwenden Sie die Funktion als Variablen und erstellen Sie eine Funktion, die die Funktion zurückgibt


13

Funktionen unterstützen keine Parameterstandards. Methoden tun. Beim Konvertieren von einer Methode in eine Funktion gehen die Standardeinstellungen der Parameter verloren. (Scala 2.8.1)


5
Gibt es einen Grund dafür?
Corazza

7

Hier gibt es einen schönen Artikel , aus dem die meisten meiner Beschreibungen stammen. Nur ein kurzer Vergleich der Funktionen und Methoden in Bezug auf mein Verständnis. Ich hoffe es hilft:

Funktionen : Sie sind im Grunde ein Objekt. Genauer gesagt sind Funktionen Objekte mit einer Apply-Methode. Daher sind sie aufgrund ihres Overheads etwas langsamer als Methoden. Es ähnelt statischen Methoden in dem Sinne, dass sie unabhängig von einem aufzurufenden Objekt sind. Ein einfaches Beispiel für eine Funktion ist wie folgt:

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

Die obige Zeile ist nichts anderes als das Zuweisen eines Objekts zu einem anderen wie object1 = object2. Tatsächlich ist das Objekt2 in unserem Beispiel eine anonyme Funktion und die linke Seite erhält aus diesem Grund den Typ eines Objekts. Daher ist jetzt f1 ein Objekt (Funktion). Die anonyme Funktion ist tatsächlich eine Instanz von Function1 [Int, Int], dh eine Funktion mit 1 Parameter vom Typ Int und einem Rückgabewert vom Typ Int. Wenn Sie f1 ohne die Argumente aufrufen, erhalten Sie die Signatur der anonymen Funktion (Int => Int =).

Methoden : Sie sind keine Objekte, sondern einer Instanz einer Klasse zugeordnet, dh einem Objekt. Genau das gleiche wie die Methode in Java oder die Member-Funktionen in c ++ (wie Raffi Khatchadourian in einem Kommentar zu dieser Frage hervorhob ) usw. Ein einfaches Beispiel für eine Methode ist wie folgt:

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

Die obige Zeile ist keine einfache Wertzuweisung, sondern eine Definition einer Methode. Wenn Sie diese Methode mit dem Wert 2 wie in der zweiten Zeile aufrufen, wird das x durch 2 ersetzt und das Ergebnis wird berechnet und Sie erhalten 4 als Ausgabe. Hier erhalten Sie eine Fehlermeldung, wenn Sie einfach m1 schreiben, da es sich um eine Methode handelt und den Eingabewert benötigt. Mit _ können Sie einer Funktion wie unten eine Methode zuweisen:

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

Was bedeutet es, "einer Funktion eine Methode zuzuweisen"? Bedeutet das nur, dass Sie jetzt ein Objekt haben, das sich genauso verhält wie die Methode?
K. M

@KM: val f2 = m1 _ ist äquivalent zu val f2 = new Funktion1 [Int, Int] {def m1 (x: Int) = x + x};
Sasuke

3

Hier ist ein großartiger Beitrag von Rob Norris, der den Unterschied erklärt, hier ist ein TL; DR

Methoden in Scala sind keine Werte, aber Funktionen. Sie können eine Funktion erstellen, die über η-Erweiterung an eine Methode delegiert (ausgelöst durch den nachfolgenden Unterstrich).

mit folgender Definition:

ein Verfahren ist etwas mit definierten def und ein Wert ist etwas , das Sie zu einem zuweisen val

Kurz gesagt ( Auszug aus dem Blog ):

Wenn wir eine Methode definieren, sehen wir, dass wir sie nicht einer zuweisen können 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

Beachten Sie auch die Art von add1, die nicht normal aussieht; Sie können keine Variable vom Typ deklarieren (n: Int)Int. Methoden sind keine Werte.

Durch Hinzufügen des Postfix-Operators für die η-Erweiterung (η wird „eta“ ausgesprochen) können wir die Methode jedoch in einen Funktionswert umwandeln. Beachten Sie die Art von f.

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

scala> f(3)
res0: Int = 4

Dies _hat zur Folge, dass Folgendes ausgeführt wird: Wir erstellen eine Function1Instanz, die an unsere Methode delegiert.

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

In Scala 2.13 können Methoden im Gegensatz zu Funktionen Methoden übernehmen / zurückgeben

  • Typparameter (polymorphe Methoden)
  • implizite Parameter
  • abhängige Typen

Diese Einschränkungen werden jedoch in dotty (Scala 3) durch die polymorphen Funktionstypen # 4672 aufgehoben. Beispielsweise aktiviert dotty Version 0.23.0-RC1 die folgende Syntax

Geben Sie Parameter ein

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

Implizite Parameter ( Kontextparameter )

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

Abhängige Typen

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

Weitere Beispiele finden Sie unter tests / run / polymorphic-functions.scala


0

In der Praxis muss ein Scala-Programmierer nur die folgenden drei Regeln kennen, um Funktionen und Methoden ordnungsgemäß verwenden zu können:

  • Durch defund Funktionsliterale definierte Methoden =>sind Funktionen. Es ist in Seite 143, Kapitel 8 des Buches Programmieren in Scala, 4. Auflage, definiert.
  • Funktionswerte sind Objekte, die als beliebige Werte weitergegeben werden können. Funktionsliterale und teilweise angewendete Funktionen sind Funktionswerte.
  • Sie können den Unterstrich einer teilweise angewendeten Funktion weglassen, wenn an einer Stelle im Code ein Funktionswert erforderlich ist. Beispielsweise:someNumber.foreach(println)

Nach vier Ausgaben von Programming in Scala ist es immer noch ein Problem für die Menschen, die beiden wichtigen Konzepte zu unterscheiden: Funktion und Funktionswert, da nicht alle Ausgaben eine klare Erklärung geben. Die Sprachspezifikation ist zu kompliziert. Ich fand die obigen Regeln einfach und genau.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.