370 Stimmen

Was ist der formale Unterschied in Scala zwischen geschweiften und geklammerten Klammern, und wann sollten sie verwendet werden?

Was ist der formale Unterschied zwischen der Übergabe von Argumenten an Funktionen in Klammern () und in Klammern {} ?

Das Gefühl, das ich bei der Programmierung in Scala Buch ist, dass Scala ziemlich flexibel ist und ich diejenige verwenden sollte, die mir am besten gefällt, aber ich stelle fest, dass einige Fälle kompiliert werden und andere nicht.

Zum Beispiel (nur als Beispiel gemeint; ich wäre dankbar für jede Antwort, die den allgemeinen Fall erörtert, nicht nur dieses spezielle Beispiel):

val tupleList = List[(String, String)]()
val filtered = tupleList.takeWhile( case (s1, s2) => s1 == s2 )

\=> Fehler: Unzulässiger Beginn eines einfachen Ausdrucks

val filtered = tupleList.takeWhile{ case (s1, s2) => s1 == s2 }

\=> gut.

399voto

Daniel C. Sobral Punkte 290004

Ich habe einmal versucht, darüber zu schreiben, aber ich habe es schließlich aufgegeben, da die Regeln etwas diffus sind. Im Grunde muss man den Dreh raus haben.

Vielleicht ist es am besten, sich darauf zu konzentrieren, wo geschweifte Klammern und Klammern austauschbar verwendet werden können: bei der Übergabe von Parametern an Methodenaufrufe. Sie mayo Klammern durch geschweifte Klammern ersetzen, wenn, und nur wenn, die Methode einen einzelnen Parameter erwartet. Zum Beispiel:

List(1, 2, 3).reduceLeft{_ + _} // valid, single Function2[Int,Int] parameter

List{1, 2, 3}.reduceLeft(_ + _) // invalid, A* vararg parameter

Es gibt jedoch noch mehr, was Sie wissen müssen, um diese Regeln besser zu verstehen.

Verbesserte Kompilierprüfung mit Parens

Die Autoren von Spray empfehlen runde Parens, weil sie eine bessere Kompilierprüfung ermöglichen. Dies ist besonders wichtig für DSLs wie Spray. Durch die Verwendung von Parens teilen Sie dem Compiler mit, dass er nur eine einzige Zeile erhalten soll; wenn Sie ihm also versehentlich zwei oder mehr geben, wird er sich beschweren. Bei geschweiften Klammern ist das nicht der Fall - wenn Sie zum Beispiel irgendwo einen Operator vergessen, wird Ihr Code kompiliert, und Sie erhalten unerwartete Ergebnisse und möglicherweise einen schwer zu findenden Fehler. Das folgende Beispiel ist konstruiert (da die Ausdrücke rein sind und zumindest eine Warnung ausgeben), verdeutlicht aber den Punkt:

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
)

Die erste kompiliert, die zweite ergibt error: ')' expected but integer literal found . Der Autor wollte schreiben 1 + 2 + 3 .

Man könnte argumentieren, dass es bei Methoden mit mehreren Parametern und Standardargumenten ähnlich ist; es ist unmöglich, bei der Verwendung von Parens versehentlich ein Komma zur Trennung der Parameter zu vergessen.

Wortbedeutung

Ein wichtiger, oft übersehener Hinweis zur Ausführlichkeit. Die Verwendung geschweifter Klammern führt unweigerlich zu ausführlichem Code, da die Scala-Style-Guide besagt eindeutig, dass schließende geschweifte Klammern in einer eigenen Zeile stehen müssen:

die schließende Klammer steht in einer eigenen Zeile unmittelbar nach dem letzten Zeile der Funktion.

Viele automatische Formatierer, wie z.B. in IntelliJ, führen diese Umformatierung automatisch für Sie durch. Versuchen Sie also, wenn möglich, runde Parens zu verwenden.

Infix-Notation

Bei Verwendung der Infix-Notation, wie List(1,2,3) indexOf (2) können Sie die Klammern weglassen, wenn es nur einen Parameter gibt, und ihn wie folgt schreiben List(1, 2, 3) indexOf 2 . Dies ist bei der Punktnotation nicht der Fall.

Beachten Sie auch, dass Sie bei einem einzelnen Parameter, der ein Ausdruck mit mehreren Token ist, wie x + 2 o a => a % 2 == 0 müssen Sie Klammern verwenden, um die Grenzen des Ausdrucks anzugeben.

Tupel

Weil man manchmal Klammern weglassen kann, braucht ein Tupel manchmal zusätzliche Klammern wie in ((1, 2)) , und manchmal kann die äußere Klammer weggelassen werden, wie in (1, 2) . Dies kann zu Verwirrung führen.

Funktion/Teilfunktion-Literale mit case

Scala hat eine Syntax für Funktions- und Teilfunktionsliterale. Sie sieht wie folgt aus:

{
    case pattern if guard => statements
    case pattern => statements
}

Die einzigen anderen Orte, an denen Sie Folgendes verwenden können case Aussagen sind mit dem match y catch Schlüsselwörter:

object match {
    case pattern if guard => statements
    case pattern => statements
}

try {
    block
} catch {
    case pattern if guard => statements
    case pattern => statements
} finally {
    block
}

Sie können nicht verwenden case Aussagen in jedem anderen Zusammenhang . Wenn Sie also Folgendes verwenden möchten case Sie brauchen geschweifte Klammern. Falls Sie sich fragen, was den Unterschied zwischen einer Funktion und einem Teilfunktionsliteral ausmacht, lautet die Antwort: der Kontext. Wenn Scala eine Funktion erwartet, erhalten Sie eine Funktion. Wenn es eine Teilfunktion erwartet, erhalten Sie eine Teilfunktion. Wenn beides erwartet wird, gibt es einen Fehler wegen Mehrdeutigkeit.

Ausdrücke und Blöcke

Klammern können verwendet werden, um Teilausdrücke zu bilden. Geschweifte Klammern können verwendet werden, um Codeblöcke zu bilden (dies ist no ein Funktionsliteral, also hüten Sie sich davor, es wie ein solches zu verwenden). Ein Codeblock besteht aus mehreren Anweisungen, von denen jede eine Importanweisung, eine Deklaration oder ein Ausdruck sein kann. Das geht etwa so:

{
    import stuff._
    statement ; // ; optional at the end of the line
    statement ; statement // not optional here
    var x = 0 // declaration
    while (x < 10) { x += 1 } // stuff
    (x % 5) + 1 // expression
}

( expression )

Wenn Sie also Deklarationen, mehrere Anweisungen, eine import oder etwas Ähnliches, brauchen Sie geschweifte Klammern. Und da ein Ausdruck eine Anweisung ist, können Klammern innerhalb geschweifter Klammern stehen. Das Interessante ist jedoch, dass Codeblöcke également Ausdrücke, so dass Sie sie überall verwenden können innerhalb einen Ausdruck:

( { var x = 0; while (x < 10) { x += 1}; x } % 5) + 1

Da also Ausdrücke Anweisungen sind und Codeblöcke Ausdrücke sind, ist alles Folgende gültig:

1       // literal
(1)     // expression
{1}     // block of code
({1})   // expression with a block of code
{(1)}   // block of code with an expression
({(1)}) // you get the drift...

Wo sie nicht austauschbar sind

Im Grunde kann man nicht ersetzen {} con () oder umgekehrt irgendwo anders. Zum Beispiel:

while (x < 10) { x += 1 }

Dies ist kein Methodenaufruf, Sie können ihn also nicht auf eine andere Weise schreiben. Nun, Sie können geschweifte Klammern setzen innerhalb die Klammer für die condition sowie die Verwendung von Klammern innerhalb die geschweiften Klammern für den Codeblock:

while ({x < 10}) { (x += 1) }

Ich hoffe, das hilft.

63voto

Theo Punkte 128508

Hier gibt es verschiedene Regeln und Schlussfolgerungen: Erstens leitet Scala die geschweiften Klammern ab, wenn ein Parameter eine Funktion ist, z.B. in list.map(_ * 2) die geschweiften Klammern werden abgeleitet, es ist nur eine kürzere Form von list.map({_ * 2}) . Zweitens erlaubt Scala, die Klammern bei der letzten Parameterliste zu überspringen, wenn diese Parameterliste einen Parameter hat und es sich um eine Funktion handelt, also list.foldLeft(0)(_ + _) kann geschrieben werden als list.foldLeft(0) { _ + _ } (oder list.foldLeft(0)({_ + _}) wenn Sie es genau wissen wollen).

Wenn Sie jedoch Folgendes hinzufügen case erhalten Sie, wie bereits erwähnt, eine Teilfunktion statt einer Funktion, und Scala leitet die geschweiften Klammern für Teilfunktionen nicht ab, so dass list.map(case x => x * 2) wird nicht funktionieren, aber beide list.map({case x => 2 * 2}) y list.map { case x => x * 2 } wird.

23voto

olle kullberg Punkte 6219

Es gibt Bemühungen der Gemeinschaft, die Verwendung von geschweiften Klammern und Klammern zu standardisieren, siehe Scala Style Guide (Seite 21): http://www.codecommit.com/scala-style-guide.pdf

Die empfohlene Syntax für Methodenaufrufe höherer Ordnung ist, immer geschweifte Klammern zu verwenden und den Punkt wegzulassen:

val filtered = tupleList takeWhile { case (s1, s2) => s1 == s2 }

Für "normale" Metadatenaufrufe sollten Sie den Punkt und die Klammern verwenden.

val result = myInstance.foo(5, "Hello")

21voto

lcn Punkte 2069

Ich glaube nicht, dass es etwas Besonderes oder Komplexes an geschweiften Klammern in Scala gibt. Um die scheinbar komplexe Verwendung von geschweiften Klammern in Scala zu meistern, müssen Sie nur ein paar einfache Dinge im Kopf behalten:

  1. geschweifte Klammern bilden einen Code-Block, der zur letzten Code-Zeile ausgewertet wird (fast alle Sprachen tun dies)
  2. eine Funktion kann auf Wunsch mit dem Codeblock erzeugt werden (folgt Regel 1)
  3. geschweifte Klammern können bei einzeiligem Code weggelassen werden, außer bei einer Case-Klausel (Wahl von Scala)
  4. Klammern können bei Funktionsaufrufen mit Codeblock als Parameter weggelassen werden (Wahl von Scala)

Lassen Sie uns einige Beispiele für die drei oben genannten Regeln erläutern:

val tupleList = List[(String, String)]()
// doesn't compile, violates case clause requirement
val filtered = tupleList.takeWhile( case (s1, s2) => s1 == s2 ) 
// block of code as a partial function and parentheses omission,
// i.e. tupleList.takeWhile({ case (s1, s2) => s1 == s2 })
val filtered = tupleList.takeWhile{ case (s1, s2) => s1 == s2 }

// curly braces omission, i.e. List(1, 2, 3).reduceLeft({_+_})
List(1, 2, 3).reduceLeft(_+_)
// parentheses omission, i.e. List(1, 2, 3).reduceLeft({_+_})
List(1, 2, 3).reduceLeft{_+_}
// not both though it compiles, because meaning totally changes due to precedence
List(1, 2, 3).reduceLeft _+_ // res1: String => String = <function1>

// curly braces omission, i.e. List(1, 2, 3).foldLeft(0)({_ + _})
List(1, 2, 3).foldLeft(0)(_ + _)
// parentheses omission, i.e. List(1, 2, 3).foldLeft(0)({_ + _})
List(1, 2, 3).foldLeft(0){_ + _}
// block of code and parentheses omission
List(1, 2, 3).foldLeft {0} {_ + _}
// not both though it compiles, because meaning totally changes due to precedence
List(1, 2, 3).foldLeft(0) _ + _
// error: ';' expected but integer literal found.
List(1, 2, 3).foldLeft 0 (_ + _)

def foo(f: Int => Unit) = { println("Entering foo"); f(4) }
// block of code that just evaluates to a value of a function, and parentheses omission
// i.e. foo({ println("Hey"); x => println(x) })
foo { println("Hey"); x => println(x) }

// parentheses omission, i.e. f({x})
def f(x: Int): Int = f {x}
// error: missing arguments for method f
def f(x: Int): Int = f x

14voto

Lukasz Korzybski Punkte 6579

Ich denke, es lohnt sich, ihre Verwendung in Funktionsaufrufen zu erläutern und zu erklären, warum verschiedene Dinge passieren. Wie bereits gesagt wurde, definieren geschweifte Klammern einen Codeblock, der auch ein Ausdruck ist und daher dort platziert werden kann, wo ein Ausdruck erwartet wird, der dann ausgewertet wird. Wenn er ausgewertet wird, werden seine Anweisungen ausgeführt und der Wert der letzten Anweisung ist das Ergebnis der Auswertung des gesamten Blocks (ähnlich wie in Ruby).

Damit können wir Dinge tun wie:

2 + { 3 }             // res: Int = 5
val x = { 4 }         // res: x: Int = 4
List({1},{2},{3})     // res: List[Int] = List(1,2,3)

Das letzte Beispiel ist nur ein Funktionsaufruf mit drei Parametern, von denen jeder zuerst ausgewertet wird.

Um nun zu sehen, wie es mit Funktionsaufrufen funktioniert, wollen wir eine einfache Funktion definieren, die eine andere Funktion als Parameter annimmt.

def foo(f: Int => Unit) = { println("Entering foo"); f(4) }

Um sie aufzurufen, müssen wir eine Funktion übergeben, die einen Parameter vom Typ Int annimmt, also können wir function literal verwenden und es an foo übergeben:

foo( x => println(x) )

Wie bereits gesagt, können wir einen Codeblock anstelle eines Ausdrucks verwenden, also verwenden wir ihn

foo({ x => println(x) })

Hier wird der Code innerhalb von {} ausgewertet, und der Funktionswert wird als Wert der Blockauswertung zurückgegeben, der dann an foo übergeben wird. Dies ist semantisch dasselbe wie der vorherige Aufruf.

Aber wir können noch etwas hinzufügen:

foo({ println("Hey"); x => println(x) })

Unser Codeblock enthält nun zwei Anweisungen, und da er vor der Ausführung von foo ausgewertet wird, wird zuerst "Hey" gedruckt, dann wird unsere Funktion an foo übergeben, "Entering foo" wird gedruckt und schließlich "4".

Das sieht allerdings etwas hässlich aus und Scala lässt uns die Klammern in diesem Fall weglassen, so dass wir schreiben können:

foo { println("Hey"); x => println(x) }

o

foo { x => println(x) }

Das sieht viel schöner aus und ist den früheren gleichwertig. Hier wird immer noch ein Codeblock zuerst ausgewertet und das Ergebnis der Auswertung (also x => println(x)) wird als Argument an foo übergeben.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X