Tentei escrever uma vez sobre isso, mas desisti no final, pois as regras são um pouco difusas. Basicamente, você terá que pegar o jeito.
Talvez seja melhor se concentrar em onde chaves e parênteses podem ser usados de forma intercambiável: ao passar parâmetros para chamadas de método. Você pode substituir parênteses por chaves se, e somente se, o método esperar um único parâmetro. Por exemplo:
List(1, 2, 3).reduceLeft{_ + _} // valid, single Function2[Int,Int] parameter
List{1, 2, 3}.reduceLeft(_ + _) // invalid, A* vararg parameter
No entanto, é preciso saber mais para entender melhor essas regras.
Maior verificação de compilação com parens
Os autores do Spray recomendam parênteses redondos porque fornecem maior verificação de compilação. Isso é especialmente importante para DSLs como o Spray. Ao usar parens, você está dizendo ao compilador que ele deve receber apenas uma única linha; portanto, se você acidentalmente der dois ou mais, ele reclamará. Agora, esse não é o caso das chaves - se, por exemplo, você esquecer um operador em algum lugar, seu código será compilado e você obterá resultados inesperados e, potencialmente, um bug muito difícil de encontrar. Abaixo é inventado (já que as expressões são puras e pelo menos darão um aviso), mas defende o argumento:
method {
1 +
2
3
}
method(
1 +
2
3
)
A primeira compila, a segunda dá error: ')' expected but integer literal found
. O autor quis escrever 1 + 2 + 3
.
Pode-se argumentar que é semelhante para métodos multiparâmetros com argumentos padrão; é impossível esquecer acidentalmente uma vírgula para separar parâmetros ao usar parênteses.
Verbosidade
Uma observação importante, muitas vezes esquecida, sobre a verbosidade. O uso de chavetas inevitavelmente leva a códigos detalhados, pois o guia de estilo Scala afirma claramente que os chavetas de fechamento devem estar em sua própria linha:
… A chave de fechamento está em sua própria linha imediatamente após a última linha da função.
Muitos reformadores automáticos, como no IntelliJ, executam automaticamente essa reformatação para você. Portanto, tente continuar usando parênteses redondas quando puder.
Notação Infix
Ao usar a notação infix, List(1,2,3) indexOf (2)
é possível omitir parênteses se houver apenas um parâmetro e escrevê-lo como List(1, 2, 3) indexOf 2
. Este não é o caso da notação de ponto.
Observe também que, quando você tem um único parâmetro que é uma expressão com vários tokens, como x + 2
ou a => a % 2 == 0
, é necessário usar parênteses para indicar os limites da expressão.
Tuplas
Como você pode omitir parênteses algumas vezes, algumas vezes uma tupla precisa de parênteses extras, como em ((1, 2))
, e algumas vezes o parêntese externo pode ser omitido, como em (1, 2)
. Isso pode causar confusão.
Literais de Função / Função Parcial com case
Scala tem uma sintaxe para literais de função e função parcial. Se parece com isso:
{
case pattern if guard => statements
case pattern => statements
}
Os únicos outros lugares onde você pode usar case
instruções são com as palavras-chave match
e catch
:
object match {
case pattern if guard => statements
case pattern => statements
}
try {
block
} catch {
case pattern if guard => statements
case pattern => statements
} finally {
block
}
Você não pode usar case
instruções em nenhum outro contexto . Então, se você quiser usar case
, precisará de chaves. Caso você esteja se perguntando o que torna literal a distinção entre uma função e uma função parcial, a resposta é: contexto. Se Scala espera uma função, você recebe uma função. Se ele espera uma função parcial, você obtém uma função parcial. Se ambos forem esperados, ocorrerá um erro sobre ambiguidade.
Expressões e blocos
Parênteses podem ser usados para fazer subexpressões. Os chavetas podem ser usadas para criar blocos de código (essa não é uma função literal, portanto, tente usá-la como uma). Um bloco de código consiste em várias instruções, cada uma das quais pode ser uma declaração de importação, uma declaração ou uma expressão. É assim:
{
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 )
Portanto, se você precisar de declarações, várias instruções import
ou algo parecido, precisará de chaves. E como uma expressão é uma declaração, parênteses podem aparecer dentro de chaves. Mas o interessante é que os blocos de código também são expressões, para que você possa usá-los em qualquer lugar dentro de uma expressão:
( { var x = 0; while (x < 10) { x += 1}; x } % 5) + 1
Portanto, como expressões são instruções e blocos de códigos são expressões, tudo abaixo é 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...
Onde eles não são intercambiáveis
Basicamente, você não pode substituir {}
com ()
ou vice-versa em qualquer outro lugar. Por exemplo:
while (x < 10) { x += 1 }
Como não é uma chamada de método, não é possível escrevê-la de nenhuma outra maneira. Bem, você pode colocar chaves dentro dos parênteses para o condition
, bem como usar parênteses dentro dos chaves para o bloco de código:
while ({x < 10}) { (x += 1) }
Então, espero que isso ajude.
{}
- tudo deve ser uma única expressão puraList{1, 2, 3}.reduceLeft(_ + _)
é inválido, você quer dizer que ele tem sintaxe errada? Mas acho que esse código pode compilar. Eu coloquei meu código aquiList(1, 2, 3)
em todos os exemplos, em vez deList{1, 2, 3}
. Infelizmente, na versão atual do Scala (2.13), isso falha com uma mensagem de erro diferente (vírgula inesperada). Você precisaria voltar para 2,7 ou 2,8 para obter o erro original, provavelmente.Existem algumas regras e inferências diferentes acontecendo aqui: em primeiro lugar, Scala infere as chaves quando um parâmetro é uma função, por exemplo, nas
list.map(_ * 2)
chaves são inferidas, é apenas uma forma mais curta delist.map({_ * 2})
. Em segundo lugar, Scala permite que você pule os parênteses na última lista de parâmetros, se essa lista de parâmetros tiver um parâmetro e for uma função, entãolist.foldLeft(0)(_ + _)
poderá ser escrita comolist.foldLeft(0) { _ + _ }
(oulist.foldLeft(0)({_ + _})
se você quiser ser mais explícito).No entanto, se você adicionar
case
, obtém, como outros já mencionaram, uma função parcial em vez de uma função, e Scala não deduzirá o suporte para funções parciais; portantolist.map(case x => x * 2)
, não funcionará, mas amboslist.map({case x => 2 * 2})
e olist.map { case x => x * 2 }
farão.fonte
list.foldLeft{0}{_+_}
funciona.Há um esforço da comunidade para padronizar o uso de chaves e parênteses, consulte o Scala Style Guide (página 21): http://www.codecommit.com/scala-style-guide.pdf
A sintaxe recomendada para chamadas de métodos de ordem superior é sempre usar chaves e pular o ponto:
Para chamadas metodológicas "normais", você deve usar o ponto e os parênteses.
fonte
+
,--
), NÃO para métodos regularestakeWhile
. O ponto inteiro da notação infix é permitir DSLs e operadores personalizados; portanto, deve-se usá-lo nesse contexto nem sempre.Eu não acho que exista algo particular ou complexo em aparelhos fixos em Scala. Para dominar o uso aparentemente complexo deles no Scala, lembre-se de algumas coisas simples:
Vamos explicar alguns exemplos de acordo com as três regras acima:
fonte
{}
comportamento. Atualizei o texto para obter precisão. E para 4, é um pouco complicado devido à interação entre()
e{}
, comodef f(x: Int): Int = f {x}
funciona, e é por isso que eu tive o quinto. :)fun f(x) = f x
é válido no SML.f {x}
of({x})
que parece ser uma explicação melhor para mim, pois pensar()
e{}
intercambiar é menos intuitivo. A propósito, af({x})
interpretação é um pouco apoiada pelas especificações do Scala (seção 6.6):ArgumentExprs ::= ‘(’ [Exprs] ‘)’ | ‘(’ [Exprs ‘,’] PostfixExpr ‘:’ ‘_’ ‘*’ ’)’ | [nl] BlockExp
Eu acho que vale a pena explicar o uso deles em chamadas de função e por que várias coisas acontecem. Como alguém já disse que chavetas definem um bloco de código, que também é uma expressão, pode ser colocado onde a expressão é esperada e será avaliada. Quando avaliadas, suas instruções são executadas e o valor da última instrução é o resultado de uma avaliação de bloco inteiro (um pouco como no Ruby).
Tendo isso, podemos fazer coisas como:
O último exemplo é apenas uma chamada de função com três parâmetros, dos quais cada um é avaliado primeiro.
Agora, para ver como funciona com chamadas de função, vamos definir funções simples que usam outra função como parâmetro.
Para chamá-lo, precisamos passar a função que usa um parâmetro do tipo Int, para que possamos usar a função literal e passá-la para foo:
Agora, como dito antes, podemos usar o bloco de código no lugar de uma expressão, então vamos usá-lo
O que acontece aqui é que o código dentro de {} é avaliado e o valor da função é retornado como um valor da avaliação do bloco; esse valor é passado para foo. É semanticamente o mesmo da chamada anterior.
Mas podemos adicionar algo mais:
Agora, nosso bloco de código contém duas instruções e, como é avaliado antes da execução do foo, o que acontece é que primeiro "Hey" é impresso, nossa função é passada para foo, "Digitando foo" é impresso e, por fim, "4" é impresso. .
Parece um pouco feio e Scala nos permite pular os parênteses nesse caso, para que possamos escrever:
ou
Isso parece muito melhor e é equivalente aos primeiros. Aqui ainda o bloco de código é avaliado primeiro e o resultado da avaliação (que é x => println (x)) é passado como argumento para foo.
fonte
foo({ x => println(x) })
. Talvez eu esteja muito preso em meus caminhos ...Como você está usando
case
, você está definindo uma função parcial e funções parciais requerem chaves.fonte
Maior verificação de compilação com parens
Os autores do Spray recomendam que as parênteses arredondadas aumentem a verificação de compilação. Isso é especialmente importante para DSLs como o Spray. Ao usar parens, você está dizendo ao compilador que ele deve receber apenas uma única linha; portanto, se você acidentalmente fornecer duas ou mais, ele irá reclamar. Agora, esse não é o caso das chaves, se, por exemplo, você esquecer um operador em algum lugar que seu código será compilado, obter resultados inesperados e potencialmente um bug muito difícil de encontrar. Abaixo é inventado (já que as expressões são puras e, pelo menos, emitem um aviso), mas defende
A primeira compila, a segunda dá
error: ')' expected but integer literal found.
ao autor que queria escrever1 + 2 + 3
.Pode-se argumentar que é semelhante para métodos multiparâmetros com argumentos padrão; é impossível esquecer acidentalmente uma vírgula para separar parâmetros ao usar parênteses.
Verbosidade
Uma observação importante, muitas vezes esquecida, sobre a verbosidade. O uso de chavetas inevitavelmente leva a códigos detalhados, pois o guia de estilo da scala indica claramente que as chaves de fechamento devem estar em sua própria linha: http://docs.scala-lang.org/style/declarations.html "... a chave de fechamento está em sua própria linha imediatamente após a última linha da função ". Muitos reformadores automáticos, como no Intellij, executam automaticamente essa reformatação para você. Portanto, tente continuar usando parênteses redondas quando puder. Por exemplo,
List(1, 2, 3).reduceLeft{_ + _}
torna-se:fonte
Com chaves, você tem ponto e vírgula induzido para você e parênteses não. Considere a
takeWhile
função, uma vez que espera função parcial, somente{case xxx => ??? }
é uma definição válida em vez de parênteses em torno da expressão do caso.fonte