Quelle est la différence formelle entre le passage d’arguments à des fonctions entre parenthèses ()
et entre accolades {}
?
Le sentiment que j'ai eu dans le livre Programmation en Scala est que Scala est assez flexible et que je devrais utiliser celui que j'aime le plus, mais je trouve que certains cas sont compilés alors que d'autres ne le sont pas.
Par exemple (juste à titre d'exemple; j'apprécierais toute réponse qui traite du cas général, pas seulement de cet exemple particulier):
val tupleList = List[(String, String)]()
val filtered = tupleList.takeWhile( case (s1, s2) => s1 == s2 )
=> erreur: début illégal d'expression simple
val filtered = tupleList.takeWhile{ case (s1, s2) => s1 == s2 }
=> bien.
J'ai essayé une fois d'écrire à ce sujet, mais j'ai finalement abandonné, les règles étant quelque peu diffuses. Fondamentalement, vous devrez vous y habituer.
Il est peut-être préférable de se concentrer sur les endroits où les accolades et les parenthèses peuvent être utilisées de manière interchangeable: lors du passage de paramètres à des appels de méthodes. Vous pouvez remplacer les parenthèses par des accolades si et seulement si la méthode attend un seul paramètre. Par exemple:
List(1, 2, 3).reduceLeft{_ + _} // valid, single Function2[Int,Int] parameter
List{1, 2, 3}.reduceLeft(_ + _) // invalid, A* vararg parameter
Cependant, vous devez en savoir plus pour mieux comprendre ces règles.
Les auteurs de Spray recommandent les arrondis car ils offrent une vérification accrue de la compilation. Ceci est particulièrement important pour les DSL comme Spray. En utilisant des parenthèses, vous dites au compilateur que vous ne devez lui donner qu'une seule ligne; par conséquent, si vous lui en donnez accidentellement deux ou plus, il se plaindra. Ce n’est plus le cas avec les accolades - si, par exemple, vous oubliez un opérateur quelque part, votre code sera compilé et vous obtiendrez des résultats inattendus et potentiellement un bug très difficile à trouver. Ci-dessous est artificiel (puisque les expressions sont pures et donneront au moins un avertissement), mais soulève le point suivant:
method {
1 +
2
3
}
method(
1 +
2
3
)
La première compile, la seconde donne error: ')' expected but integer literal found
. L'auteur voulait écrire 1 + 2 + 3
.
On pourrait dire que c’est similaire pour les méthodes multi-paramètres avec des arguments par défaut; il est impossible d’oublier accidentellement une virgule pour séparer les paramètres lors de l’utilisation de parens.
Une note importante souvent négligée à propos de la verbosité. L'utilisation d'accolades conduit inévitablement à un code prolixe puisque = Guide de style Scala indique clairement que les accolades fermantes doivent se trouver sur leur propre ligne:
… L'accolade de fermeture est sur sa propre ligne immédiatement après la dernière ligne de la fonction.
De nombreux auto-reformateurs, comme dans IntelliJ, effectueront automatiquement ce reformatage pour vous. Donc, essayez de vous en tenir à utiliser des parenthèses rondes quand vous le pouvez.
Lorsque vous utilisez la notation infixe, comme List(1,2,3) indexOf (2)
, vous pouvez omettre la parenthèse s'il n'y a qu'un seul paramètre et l'écrire sous la forme List(1, 2, 3) indexOf 2
. Ce n'est pas le cas de la notation par points.
Notez également que lorsque vous avez un seul paramètre qui est une expression à plusieurs jetons, comme x + 2
Ou a => a % 2 == 0
, Vous devez utiliser une parenthèse pour indiquer les limites de l'expression.
Comme vous pouvez parfois omettre des parenthèses, un tuple a parfois besoin de parenthèses supplémentaires, comme dans ((1, 2))
, Et parfois, la parenthèse externe peut être omise, comme dans (1, 2)
. Cela peut causer de la confusion.
case
Scala a une syntaxe pour les littéraux de fonction et de fonction partielle. Cela ressemble à ceci:
{
case pattern if guard => statements
case pattern => statements
}
Les seuls autres endroits où vous pouvez utiliser les instructions case
sont les mots-clés match
et catch
:
object match {
case pattern if guard => statements
case pattern => statements
}
try {
block
} catch {
case pattern if guard => statements
case pattern => statements
} finally {
block
}
Vous ne pouvez pas utiliser les instructions case
dans un autre contexte . Donc, si vous voulez utiliser case
, vous avez besoin de accolades. Si vous vous demandez ce qui fait la distinction entre une fonction et un littéral de fonction partielle, la réponse est: contexte. Si Scala attend une fonction, une fonction que vous obtenez. S'il attend une fonction partielle, vous obtenez une fonction partielle. Si les deux sont attendus, cela donne une erreur concernant l'ambiguïté.
La parenthèse peut être utilisée pour faire des sous-expressions. Les accolades peuvent être utilisées pour créer des blocs de code (il s’agit et non d’une fonction, alors méfiez-vous, essayez de l’utiliser comme tel). Un bloc de code est composé de plusieurs instructions, chacune pouvant être une instruction d'importation, une déclaration ou une expression. Ça va comme ça:
{
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 )
Donc, si vous avez besoin de déclarations, de déclarations multiples, d'un import
ou de quelque chose du genre, vous avez besoin d'accolades. Et comme une expression est une déclaration, les parenthèses peuvent apparaître entre des accolades. Mais l’intéressant est que les blocs de code sont aussi des expressions , de sorte que vous pouvez les utiliser n’importe où dans une expression:
( { var x = 0; while (x < 10) { x += 1}; x } % 5) + 1
Donc, puisque les expressions sont des instructions et les blocs de code sont des expressions, tout ce qui suit est valable:
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...
En gros, vous ne pouvez pas remplacer {}
Par ()
Ni l’inverse ailleurs. Par exemple:
while (x < 10) { x += 1 }
Ce n’est pas un appel de méthode, vous ne pouvez donc pas l’écrire autrement. Eh bien, vous pouvez mettre des accolades à l'intérieur de la parenthèse pour le condition
, ainsi que des parenthèses à l'intérieur des accolades pour le bloc de code:
while ({x < 10}) { (x += 1) }
Donc, j'espère que cela aide.
Il existe plusieurs règles et déductions différentes: premièrement, Scala déduit les accolades lorsqu'un paramètre est une fonction, par exemple, dans list.map(_ * 2)
, les accolades sont inférées, c'est juste une forme plus courte de list.map({_ * 2})
. Deuxièmement, Scala vous permet de sauter les parenthèses sur la dernière liste de paramètres, si cette liste de paramètres a un paramètre et que c'est une fonction, donc list.foldLeft(0)(_ + _)
peut être écrit sous la forme list.foldLeft(0) { _ + _ }
(ou list.foldLeft(0)({_ + _})
si vous souhaitez être très explicite).
Toutefois, si vous ajoutez case
, vous obtenez, comme d'autres l'ont mentionné, une fonction partielle au lieu d'une fonction, et Scala ne déduira pas les accolades des fonctions partielles, donc list.map(case x => x * 2)
ne fonctionnera pas, mais list.map({case x => 2 * 2})
et list.map { case x => x * 2 }
Fonctionneront.
La communauté s’efforce de normaliser l’utilisation des accolades et des parenthèses, voir Scala Style Guide (page 21): http://www.codecommit.com/scala- style-guide.pdf
La syntaxe recommandée pour les appels de méthodes d'ordre supérieur est de toujours utiliser des accolades et d'ignorer le point:
val filtered = tupleList takeWhile { case (s1, s2) => s1 == s2 }
Pour les appels de méthode "normaux", vous devez utiliser le point et les parenthèses.
val result = myInstance.foo(5, "Hello")
Je ne pense pas qu'il y ait quoi que ce soit de particulier ou de complexe concernant les accolades à Scala. Pour maîtriser leur apparence complexe à Scala, gardez à l’esprit quelques points simples:
Expliquons quelques exemples selon les trois règles ci-dessus:
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
Je pense qu'il vaut la peine d'expliquer leur utilisation dans les appels de fonctions et pourquoi diverses choses se produisent. Comme quelqu'un l'a déjà dit, les accolades définissent un bloc de code, qui est également une expression, de sorte qu'il peut être placé là où une expression est attendue et qu'elle sera évaluée. Lors de l'évaluation, ses instructions sont exécutées et la dernière valeur de l'instruction est le résultat de l'évaluation de tout le bloc (un peu comme dans Ruby).
Ayant cela, nous pouvons faire des choses comme:
2 + { 3 } // res: Int = 5
val x = { 4 } // res: x: Int = 4
List({1},{2},{3}) // res: List[Int] = List(1,2,3)
Le dernier exemple est juste un appel de fonction avec trois paramètres, dont chacun est évalué en premier.
Maintenant, pour voir comment cela fonctionne avec les appels de fonction, définissons une fonction simple qui prend une autre fonction en paramètre.
def foo(f: Int => Unit) = { println("Entering foo"); f(4) }
Pour l'appeler, nous devons passer une fonction qui prend un paramètre de type Int afin de pouvoir utiliser la fonction literal et le transmettre à foo:
foo( x => println(x) )
Maintenant, comme dit précédemment, nous pouvons utiliser un bloc de code à la place d'une expression, alors utilisons-le
foo({ x => println(x) })
Ce qui se passe ici, c'est que le code à l'intérieur de {} est évalué et que la valeur de la fonction est renvoyée en tant que valeur de l'évaluation du bloc. Cette valeur est ensuite transmise à foo. Ceci est sémantiquement identique à l'appel précédent.
Mais nous pouvons ajouter quelque chose de plus:
foo({ println("Hey"); x => println(x) })
Maintenant, notre bloc de code contient deux instructions et, comme il est évalué avant que foo ne soit exécuté, le premier "Hey" est imprimé, puis notre fonction est transmise à foo, "Saisie de foo" est imprimé et enfin "4" est imprimé. .
Cela semble un peu moche cependant et Scala nous permet de sauter la parenthèse dans ce cas, nous pouvons donc écrire:
foo { println("Hey"); x => println(x) }
ou
foo { x => println(x) }
Cela a l'air beaucoup plus joli et est équivalent aux anciens. Ici encore, le bloc de code est évalué en premier et le résultat de l'évaluation (x => println (x)) est passé en argument à foo.
Comme vous utilisez case
, vous définissez une fonction partielle et les fonctions partielles nécessitent des accolades.
Augmentation de la vérification de la compilation avec parens
Les auteurs de Spray recommandent aux parens ronds d’augmenter le contrôle de la compilation. Ceci est particulièrement important pour les DSL comme Spray. En utilisant des parenthèses, vous dites au compilateur qu'il ne devrait recevoir qu'une seule ligne. Par conséquent, si vous en donnez accidentellement deux ou plus, il se plaindra. Maintenant, ce n'est pas le cas des accolades. Si, par exemple, vous oubliez un opérateur là où votre code sera compilé, vous obtenez des résultats inattendus et potentiellement un bug très difficile à trouver. Ci-dessous est artificiel (puisque les expressions sont pures et donneront au moins un avertissement), mais rend le point
method {
1 +
2
3
}
method(
1 +
2
3
)
La première compile, la seconde donne error: ')' expected but integer literal found.
L'auteur voulait écrire 1 + 2 + 3
.
On pourrait dire que c'est similaire pour les méthodes multi-paramètres avec des arguments par défaut; il est impossible d'oublier accidentellement une virgule pour séparer les paramètres lorsque vous utilisez des parens.
Verbosité
Une note importante souvent négligée à propos de la verbosité. L'utilisation d'accolades conduit inévitablement au code prolixe puisque le guide de style scala) indique clairement que les accolades fermantes doivent se trouver sur leur propre ligne: http://docs.scala-lang.org /style/declarations.html "... l'accolade fermante est sur sa propre ligne immédiatement après la dernière ligne de la fonction." De nombreux auto-reformateurs, comme dans Intellij, effectueront automatiquement ce reformatage pour vous. Essayez donc pour vous en tenir à utiliser des parenthèses quand vous le pouvez. Par exemple, List(1, 2, 3).reduceLeft{_ + _}
devient:
List(1, 2, 3).reduceLeft {
_ + _
}