Qual è la differenza formale in Scala tra parentesi graffe e parentesi e quando dovrebbero essere utilizzate?

Qual è la differenza formale tra il passare gli argomenti alle funzioni tra parentesi () e tra parentesi {} ?

La sensazione che ho ricevuto dal libro Programming in Scala è che Scala è piuttosto flessibile e dovrei usare quello che mi piace di più, ma trovo che alcuni casi vengano compilati mentre altri no.

Ad esempio (inteso solo come esempio, apprezzerei qualsiasi risposta che discute il caso generale, non solo questo particolare esempio):

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

=> errore: avvio illegale di un’espressione semplice

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

=> bene.

Ho provato una volta a scrivere su questo, ma alla fine ho rinunciato, dato che le regole sono piuttosto diffuse. Fondamentalmente, dovrai capire come funziona.

Forse è meglio concentrarsi su dove le parentesi graffe e le parentesi possono essere utilizzate in modo intercambiabile: quando si passano i parametri alle chiamate di metodo. È ansible sostituire le parentesi con parentesi graffe se, e solo se, il metodo prevede un singolo parametro. Per esempio:

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

Tuttavia, è necessario conoscere meglio per comprendere meglio queste regole.

Maggiore controllo di compilazione con parens

Gli autori di Spray consigliano parenti rotondi perché danno un maggiore controllo di compilazione. Questo è particolarmente importante per i DSL come Spray. Usando Parens stai dicendo al compilatore che dovrebbe essere data una sola riga; quindi se accidentalmente gliene danno due o più, si lamenterà. Ora questo non è il caso delle parentesi graffe: se ad esempio si dimentica un operatore da qualche parte, il codice verrà compilato e si otterranno risultati imprevisti e potenzialmente un bug molto difficile da trovare. Qui di seguito è ideato (poiché le espressioni sono puri e darà almeno un avvertimento), ma rende il punto:

 method { 1 + 2 3 } method( 1 + 2 3 ) 

Il primo si compila, il secondo dà error: ')' expected but integer literal found . L’autore voleva scrivere 1 + 2 + 3 .

Si potrebbe sostenere che è simile per i metodi multiparametrici con argomenti predefiniti; è imansible dimenticare accidentalmente una virgola per separare i parametri quando si utilizza Parens.

Verbosità

Un’importante nota spesso trascurata sulla verbosità. L’utilizzo di parentesi graffe porta inevitabilmente a un codice dettagliato poiché la guida allo stile Scala indica chiaramente che la chiusura delle parentesi graffe deve essere sulla propria linea:

… la parentesi di chiusura si trova sulla propria riga immediatamente dopo l’ultima riga della funzione.

Molti riformattatori automatici, come in IntelliJ, eseguiranno automaticamente questa riformattazione per te. Quindi cerca di attenersi all’utilizzo di round parens quando puoi.

Notazione infissa

Quando si utilizza la notazione infissa, come List(1,2,3) indexOf (2) è ansible omettere la parentesi se è presente un solo parametro e scriverlo come List(1, 2, 3) indexOf 2 . Questo non è il caso della notazione a punti.

Si noti inoltre che quando si ha un singolo parametro che è un’espressione multi-token, come x + 2 o a => a % 2 == 0 , è necessario utilizzare la parentesi per indicare i limiti dell’espressione.

Le tuple

Poiché a volte è ansible omettere la parentesi, a volte una tupla richiede una parentesi aggiuntiva come in ((1, 2)) e talvolta la parentesi esterna può essere omessa, come in (1, 2) . Ciò potrebbe causare confusione.

Letterali funzione / funzione parziale con case

Scala ha una syntax per i letterali di funzione e di funzione parziale. Sembra questo:

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

Gli unici altri posti in cui è ansible utilizzare le dichiarazioni del case sono con la match e le parole chiave:

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

Non è ansible utilizzare le dichiarazioni del case in nessun altro contesto . Quindi, se vuoi usare case , hai bisogno di parentesi graffe. Nel caso ti stia chiedendo cosa faccia la distinzione tra una funzione e una funzione parziale letterale, la risposta è: contesto. Se Scala si aspetta una funzione, una funzione che ottieni. Se si aspetta una funzione parziale, si ottiene una funzione parziale. Se entrambi sono attesi, dà un errore riguardo all’ambiguità.

Espressioni e blocchi

Le parentesi possono essere utilizzate per creare sottoespressioni. Le parentesi graffe possono essere utilizzate per creare blocchi di codice ( non si tratta di una funzione letterale, quindi fai attenzione a provare a utilizzarla come tale). Un blocco di codice è composto da più istruzioni, ognuna delle quali può essere un’istruzione import, una dichiarazione o un’espressione. Va così:

 { 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 ) 

Quindi, se hai bisogno di dichiarazioni, dichiarazioni multiple, import o qualcosa del genere, hai bisogno di parentesi graffe. E poiché un'espressione è un'affermazione, le parentesi graffe possono apparire tra parentesi. Ma la cosa interessante è che i blocchi di codice sono anche espressioni, quindi puoi usarli ovunque all'interno di un'espressione:

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

Quindi, poiché le espressioni sono affermazioni e i blocchi di codici sono espressioni, tutto quanto segue è valido:

 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... 

Dove non sono intercambiabili

Fondamentalmente, non puoi sostituire {} con () o viceversa altrove. Per esempio:

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

Questa non è una chiamata al metodo, quindi non puoi scriverla in nessun altro modo. Bene, puoi mettere parentesi graffe all'interno della parentesi per la condition , così come utilizzare parentesi all'interno delle parentesi graffe per il blocco di codice:

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

Quindi, spero che questo aiuti.

Ci sono un paio di regole e inferenze diverse in corso qui: prima di tutto, Scala infila le parentesi quando un parametro è una funzione, ad esempio in list.map(_ * 2) le parentesi sono inferite, è solo una forma più breve di list.map({_ * 2}) . In secondo luogo, Scala consente di saltare le parentesi sull’ultimo elenco di parametri, se l’elenco di parametri ha un parametro ed è una funzione, quindi list.foldLeft(0)(_ + _) può essere scritto come list.foldLeft(0) { _ + _ } (o list.foldLeft(0)({_ + _}) se vuoi essere più esplicito).

Tuttavia, se si aggiunge il case si ottiene, come altri hanno già detto, una funzione parziale invece di una funzione, e Scala non dedurrà le parentesi per le funzioni parziali, quindi list.map(case x => x * 2) non funzionerà , ma entrambi list.map({case x => 2 * 2}) e list.map { case x => x * 2 } faranno.

Vi è uno sforzo da parte della comunità per standardizzare l’uso di parentesi graffe e parentesi, vedere Scala Style Guide (pagina 21): http://www.codecommit.com/scala-style-guide.pdf

La syntax consigliata per le chiamate dei metodi di ordine superiore consiste nell’utilizzare sempre le parentesi e saltare il punto:

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

Per le chiamate “normali” si dovrebbe usare il punto e le parentesi.

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

Penso che valga la pena di spiegare il loro utilizzo nelle chiamate di funzione e il motivo per cui accadono varie cose. Come qualcuno ha già detto che le parentesi graffe definiscono un blocco di codice, che è anche un’espressione, quindi può essere messo dove l’espressione è prevista e sarà valutata. Quando viene valutato, le sue istruzioni vengono eseguite e il valore dell’ultima dichiarazione è il risultato di una valutazione a blocchi interi (un po ‘come in Ruby).

Avendo ciò possiamo fare cose come:

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

L’ultimo esempio è solo una chiamata di funzione con tre parametri, di cui ciascuno viene valutato per primo.

Ora per vedere come funziona con le chiamate di funzione, definiamo una funzione semplice che prende un’altra funzione come parametro.

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

Per chiamarlo, dobbiamo passare una funzione che richiede un parametro di tipo Int, quindi possiamo usare la funzione letterale e passarla a foo:

 foo( x => println(x) ) 

Ora come detto prima possiamo usare il blocco di codice al posto di un’espressione, quindi usiamolo

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

Quello che succede qui è che il codice dentro {} viene valutato, e il valore della funzione viene restituito come valore della valutazione del blocco, questo valore viene quindi passato a foo. Questo è semanticamente uguale alla precedente.

Ma possiamo aggiungere qualcosa di più:

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

Ora il nostro blocco di codice contiene due istruzioni e poiché viene valutato prima che foo venga eseguito, ciò che accade è che viene stampato prima “Hey”, quindi la nostra funzione viene passata a foo, “Entering foo” viene stampato e infine viene stampato “4” .

Questo sembra un po ‘brutto e Scala ci permette di saltare la parentesi in questo caso, così possiamo scrivere:

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

o

 foo { x => println(x) } 

Sembra molto più bello ed è equivalente a quelli precedenti. Qui viene ancora valutato prima il blocco del codice e il risultato della valutazione (che è x => println (x)) viene passato come argomento a foo.

Non penso che ci sia qualcosa di particolare o complesso sulle parentesi graffe in Scala. Per padroneggiare l’uso apparentemente complesso di loro in Scala, tieni a mente un paio di cose semplici:

  1. le parentesi graffe formano un blocco di codice, che valuta l’ultima riga di codice (quasi tutte le lingue lo fanno)
  2. una funzione se desiderato può essere generata con il blocco di codice (segue la regola 1)
  3. le parentesi graffe possono essere omesse per il codice a una riga tranne che per una clausola case (scelta Scala)
  4. le parentesi possono essere omesse nella chiamata di funzione con il blocco di codice come parametro (scelta Scala)

Spieghiamo un paio di esempi per le tre regole precedenti:

 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, // ie tupleList.takeWhile({ case (s1, s2) => s1 == s2 }) val filtered = tupleList.takeWhile{ case (s1, s2) => s1 == s2 } // curly braces omission, ie List(1, 2, 3).reduceLeft({_+_}) List(1, 2, 3).reduceLeft(_+_) // parentheses omission, ie 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 =  // curly braces omission, ie List(1, 2, 3).foldLeft(0)({_ + _}) List(1, 2, 3).foldLeft(0)(_ + _) // parentheses omission, ie 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 // ie foo({ println("Hey"); x => println(x) }) foo { println("Hey"); x => println(x) } // parentheses omission, ie f({x}) def f(x: Int): Int = f {x} // error: missing arguments for method f def f(x: Int): Int = fx 

Perché stai usando case , stai definendo una funzione parziale e le funzioni parziali richiedono parentesi graffe.

Maggiore controllo di compilazione con parens

Gli autori di Spray, raccomandano che i parents rotondi forniscano un maggiore controllo di compilazione. Questo è particolarmente importante per i DSL come Spray. Usando Parens stai dicendo al compilatore che dovrebbe essere data una sola riga, quindi se accidentalmente gliene date due o più, si lamenterà. Ora questo non è il caso delle parentesi graffe, se per esempio si dimentica un operatore da qualche parte il codice verrà compilato, si ottengono risultati imprevisti e potenzialmente un bug molto difficile da trovare. Qui sotto è artificioso (poiché le espressioni sono pure e daranno almeno un avvertimento), ma fa il punto

 method { 1 + 2 3 } method( 1 + 2 3 ) 

Il primo si compila, il secondo dà error: ')' expected but integer literal found. l’autore voleva scrivere 1 + 2 + 3 .

Si potrebbe sostenere che è simile per i metodi multiparametrici con argomenti predefiniti; è imansible dimenticare accidentalmente una virgola per separare i parametri quando si utilizza Parens.

Verbosità

Un’importante nota spesso trascurata sulla verbosità. L’utilizzo di parentesi graffe porta inevitabilmente a un codice prolisso poiché la guida dello stile scala indica chiaramente che la chiusura delle parentesi graffe deve essere sulla propria riga: http://docs.scala-lang.org/style/declarations.html “… la parentesi di chiusura si trova sulla propria riga immediatamente dopo l’ultima riga della funzione. ” Molti riformattatori automatici, come in Intellij, eseguiranno automaticamente questa riformattazione per te. Quindi cerca di attenersi all’utilizzo di round parens quando puoi. Ad es. List(1, 2, 3).reduceLeft{_ + _} diventa:

 List(1, 2, 3).reduceLeft { _ + _ } 

Con le parentesi graffe, si ottiene il punto e virgola indotto per te e le parentesi no. Prendi in takeWhile funzione takeWhile , poiché si aspetta una funzione parziale, solo {case xxx => ??? } {case xxx => ??? } è una definizione valida al posto delle parentesi attorno all’espressione case.