Una vez intenté escribir sobre esto, pero al final me di por vencido, ya que las reglas son algo difusas. Básicamente, tendrás que acostumbrarte.
Quizás sea mejor concentrarse en dónde las llaves y los paréntesis se pueden usar indistintamente: al pasar parámetros a llamadas a métodos. Usted puede sustituir el paréntesis con llaves si, y sólo si, el método espera un solo parámetro. Por ejemplo:
List(1, 2, 3).reduceLeft{_ + _} // valid, single Function2[Int,Int] parameter
List{1, 2, 3}.reduceLeft(_ + _) // invalid, A* vararg parameter
Sin embargo, hay más que necesita saber para comprender mejor estas reglas.
Mayor verificación de compilación con parens
Los autores de Spray recomiendan parens redondos porque brindan una mayor verificación de compilación. Esto es especialmente importante para DSL como Spray. Al usar parens le está diciendo al compilador que solo se le debe dar una sola línea; por lo tanto, si accidentalmente le da dos o más, se quejará. Ahora, este no es el caso con las llaves: si, por ejemplo, olvida a un operador en algún lugar, su código se compilará y obtendrá resultados inesperados y, potencialmente, un error muy difícil de encontrar. A continuación se inventa (ya que las expresiones son puras y al menos darán una advertencia), pero hace el punto:
method {
1 +
2
3
}
method(
1 +
2
3
)
El primero compila, el segundo da error: ')' expected but integer literal found
. El autor quería escribir 1 + 2 + 3
.
Se podría argumentar que es similar para los métodos de parámetros múltiples con argumentos predeterminados; Es imposible olvidar accidentalmente una coma para separar los parámetros cuando se usan parens.
Verbosidad
Una nota importante a menudo pasada por alto sobre la verbosidad. El uso de llaves se lleva inevitablemente a un código detallado, ya que la guía de estilo Scala establece claramente que el cierre de llaves debe estar en su propia línea:
... la llave de cierre está en su propia línea inmediatamente después de la última línea de la función.
Muchos reformateadores automáticos, como en IntelliJ, realizarán automáticamente este reformateo por usted. Por lo tanto, trate de usar parens redondos cuando pueda.
Notación de infijo
Al usar la notación infija, como List(1,2,3) indexOf (2)
puede omitir paréntesis si solo hay un parámetro y escribirlo como List(1, 2, 3) indexOf 2
. Este no es el caso de la notación de puntos.
Tenga en cuenta también que cuando tiene un único parámetro que es una expresión de múltiples tokens, como x + 2
o a => a % 2 == 0
, debe usar paréntesis para indicar los límites de la expresión.
Tuplas
Debido a que a veces puede omitir paréntesis, a veces una tupla necesita paréntesis adicionales como en ((1, 2))
, y a veces el paréntesis externo puede omitirse, como en (1, 2)
. Esto puede causar confusión.
Literales de función / función parcial con case
Scala tiene una sintaxis para funciones y literales de funciones parciales. Se parece a esto:
{
case pattern if guard => statements
case pattern => statements
}
Los únicos otros lugares donde puede usar case
declaraciones son con las palabras clave match
y catch
:
object match {
case pattern if guard => statements
case pattern => statements
}
try {
block
} catch {
case pattern if guard => statements
case pattern => statements
} finally {
block
}
No puede usar case
declaraciones en ningún otro contexto . Entonces, si quieres usar case
, necesitas llaves. En caso de que se pregunte qué hace que la distinción entre una función y una función parcial sea literal, la respuesta es: contexto. Si Scala espera una función, una función que obtienes. Si espera una función parcial, obtienes una función parcial. Si se esperan ambos, da un error sobre la ambigüedad.
Expresiones y Bloques
El paréntesis se puede usar para hacer subexpresiones. Las llaves se pueden usar para hacer bloques de código (esta no es una función literal, así que tenga cuidado de intentar usarla como tal). Un bloque de código consta de varias declaraciones, cada una de las cuales puede ser una declaración de importación, una declaración o una expresión. Dice así:
{
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 )
Entonces, si necesita declaraciones, declaraciones múltiples, import
o algo así, necesita llaves. Y debido a que una expresión es una declaración, los paréntesis pueden aparecer dentro de llaves. Pero lo interesante es que los bloques de código también son expresiones, por lo que puede usarlos en cualquier lugar dentro de una expresión:
( { var x = 0; while (x < 10) { x += 1}; x } % 5) + 1
Entonces, dado que las expresiones son declaraciones y los bloques de códigos son expresiones, todo lo siguiente es válido:
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...
Donde no son intercambiables
Básicamente, no se puede reemplazar {}
con ()
o viceversa en cualquier otro lugar. Por ejemplo:
while (x < 10) { x += 1 }
Esta no es una llamada al método, por lo que no puede escribirla de ninguna otra manera. Bueno, puedes poner llaves dentro del paréntesis para el condition
, así como usar paréntesis dentro de las llaves para el bloque de código:
while ({x < 10}) { (x += 1) }
Entonces, espero que esto ayude.
{}
, todo debería ser una sola expresión puraList{1, 2, 3}.reduceLeft(_ + _)
que no es válido, ¿quieres decir que tiene una sintaxis err? Pero encuentro que el código puede compilarse. Puse mi código aquíList(1, 2, 3)
en todos los ejemplos, en lugar deList{1, 2, 3}
. Por desgracia, en la versión actual de Scala (2.13), esto falla con un mensaje de error diferente (coma inesperado). Probablemente tendría que volver a 2.7 o 2.8 para obtener el error original.Aquí hay un par de reglas e inferencias diferentes: en primer lugar, Scala infiere las llaves cuando un parámetro es una función, por ejemplo, en
list.map(_ * 2)
las llaves se infiere, es solo una forma más corta delist.map({_ * 2})
. En segundo lugar, Scala le permite omitir los paréntesis en la última lista de parámetros, si esa lista de parámetros tiene un parámetro y es una función,list.foldLeft(0)(_ + _)
puede escribirse comolist.foldLeft(0) { _ + _ }
(olist.foldLeft(0)({_ + _})
si desea ser más explícito).Sin embargo, si se agrega
case
que se obtiene, como otros han mencionado, una función parcial en lugar de una función, y Scala no inferir los apoyos para las funciones parciales, por lo quelist.map(case x => x * 2)
no va a funcionar, pero a la vezlist.map({case x => 2 * 2})
ylist.map { case x => x * 2 }
lo hará.fuente
list.foldLeft{0}{_+_}
funciona.Hay un esfuerzo de la comunidad para estandarizar el uso de llaves y paréntesis, consulte la Guía de estilo de Scala (página 21): http://www.codecommit.com/scala-style-guide.pdf
La sintaxis recomendada para las llamadas a métodos de orden superior es usar siempre llaves y omitir el punto:
Para las llamadas metodológicas "normales" debe usar el punto y los paréntesis.
fuente
+
,--
), NO métodos regulares comotakeWhile
. El punto completo de la notación infija es permitir DSL y operadores personalizados, por lo tanto, uno debe usarlo en este contexto no todo el tiempo.No creo que haya nada particular o complejo sobre las llaves en Scala. Para dominar el uso aparentemente complejo de ellos en Scala, solo tenga en cuenta un par de cosas simples:
Expliquemos un par de ejemplos según las tres reglas anteriores:
fuente
{}
comportamiento. He actualizado la redacción para mayor precisión. Y para 4, es un poco complicado debido a la interacción entre()
y{}
, comodef f(x: Int): Int = f {x}
funciona, y es por eso que tuve el 5to. :)fun f(x) = f x
es válido en SML.f {x}
comof({x})
parece ser una mejor explicación para mí, ya que pensar()
e{}
intercambiar es menos intuitivo. Por cierto, laf({x})
interpretación está algo respaldada por la especificación de Scala (sección 6.6):ArgumentExprs ::= ‘(’ [Exprs] ‘)’ | ‘(’ [Exprs ‘,’] PostfixExpr ‘:’ ‘_’ ‘*’ ’)’ | [nl] BlockExp
Creo que vale la pena explicar su uso en las llamadas a funciones y por qué ocurren varias cosas. Como alguien ya dijo, las llaves definen un bloque de código, que también es una expresión, por lo que se puede colocar donde se espera la expresión y se evaluará. Cuando se evalúa, sus declaraciones se ejecutan y el valor de la última declaración es el resultado de la evaluación del bloque completo (algo así como en Ruby).
Teniendo eso, podemos hacer cosas como:
El último ejemplo es solo una llamada de función con tres parámetros, de los cuales cada uno se evalúa primero.
Ahora, para ver cómo funciona con las llamadas a funciones, definamos una función simple que tome otra función como parámetro.
Para llamarlo, necesitamos pasar la función que toma un parámetro de tipo Int, para que podamos usar la función literal y pasarla a foo:
Ahora, como se dijo antes, podemos usar un bloque de código en lugar de una expresión, así que usémoslo
Lo que sucede aquí es que se evalúa el código dentro de {}, y el valor de la función se devuelve como un valor de la evaluación del bloque, este valor luego se pasa a foo. Esto es semánticamente igual que la llamada anterior.
Pero podemos agregar algo más:
Ahora nuestro bloque de código contiene dos declaraciones, y debido a que se evalúa antes de que se ejecute foo, lo que sucede es que primero se imprime "Hey", luego se pasa nuestra función a foo, se imprime "Enter foo" y finalmente se imprime "4" .
Sin embargo, esto parece un poco feo y Scala nos permite omitir el paréntesis en este caso, para que podamos escribir:
o
Eso se ve mucho mejor y es equivalente a los anteriores. Aquí todavía se evalúa primero el bloque de código y el resultado de la evaluación (que es x => println (x)) se pasa como argumento a foo.
fuente
foo({ x => println(x) })
. Tal vez estoy demasiado atrapado en mis caminos ...Debido a que está utilizando
case
, está definiendo una función parcial y las funciones parciales requieren llaves.fuente
Mayor verificación de compilación con parens
Los autores de Spray recomiendan que los parens redondos proporcionen una mayor verificación de compilación. Esto es especialmente importante para DSL como Spray. Al usar parens, le está diciendo al compilador que solo se le debe dar una sola línea, por lo tanto, si accidentalmente le dio dos o más, se quejará. Ahora, este no es el caso con las llaves, si, por ejemplo, olvida a un operador en algún lugar donde su código se compilará, obtendrá resultados inesperados y potencialmente un error muy difícil de encontrar. A continuación se inventa (ya que las expresiones son puras y al menos darán una advertencia), pero hace el punto
El primero compila, el segundo da
error: ')' expected but integer literal found.
al autor que quería escribir1 + 2 + 3
.Se podría argumentar que es similar para los métodos de parámetros múltiples con argumentos predeterminados; Es imposible olvidar accidentalmente una coma para separar los parámetros cuando se usan parens.
Verbosidad
Una nota importante a menudo pasada por alto sobre la verbosidad. El uso de llaves se convierte inevitablemente en un código detallado ya que la guía de estilo scala establece claramente que las llaves cerradas deben estar en su propia línea: http://docs.scala-lang.org/style/declarations.html "... la llave de cierre está en su propia línea inmediatamente después de la última línea de la función ". Muchos reformateadores automáticos, como en Intellij, realizarán automáticamente este reformateo por usted. Por lo tanto, trate de usar parens redondos cuando pueda. Por ejemplo, se
List(1, 2, 3).reduceLeft{_ + _}
convierte en:fuente
Con llaves, tienes punto y coma inducido para ti y paréntesis no. Considere la
takeWhile
función, ya que espera una función parcial, solo{case xxx => ??? }
es una definición válida en lugar de paréntesis alrededor de la expresión de mayúsculas y minúsculas.fuente